I'm on page 60 of the Hands On Dark Basic Pro volume 1 book, for implementing commands and stuff... I've jumped around a little bit, but i have
- print,
- text
- cls
- ink
- rgb
- input
- center text
- set text font
- wait key
and a few other commands all working. I'm getting pretty confident now that the command language I've built up is capable. I just need to handle a bit more complex "optional" scheme to support commands like "INPUT" that take an optional parameter as their _first_ parameter... Eh...
To a give a sense of what some of these commands look like in C#, I thought I'd share a few...
[DarkBasicCommand("rnd")]
public static int Random(int max)
{
return (int)(UnityEngine.Random.value * max);
}
[DarkBasicCommand("inc")]
public static void Increment(ref int value, int amount = 1)
{
value += amount;
}
[DarkBasicCommand("cls")]
public static void ClearScreen([FromVm] GobVirtualMachine vm, int color=0)
{
DBInputCommands.ConvertToColor(color, out var bgColor);
vm.Owner.ClearScreen(bgColor);
}
[DarkBasicCommand("print")]
public static void Print([FromVm] GobVirtualMachine vm, params object[] args)
{
vm.Owner.Print(string.Join("", args));
}
[DarkBasicCommand("text")]
public static void Text([FromVm] GobVirtualMachine vm, int x, int y, string text)
{
vm.Owner.Text(x, -y, text);
}
If you take a look, you'll see that you write C# methods fairly normally, with a few interesting caveats... You can get access to the underlying VM using the `[FromVm]` attribute. And you can use params, and optionals, and ref parameters...
However, because I'm running this inside Unity, I want Unity to be able to keep running while the DBP emulator is stalled waiting for something, such as the INPUT command.
The input implementation is the most complicated so far, and gets access to the value parameter using a special `RawArg` style. The `RawArg` is treated as a ref parameter, but allows the C# developer to manually jump into the VM and set the value of the parameter whenever they want. Chaos would reign if this was a multi-threaded system, luckily it isn't!!!
[DarkBasicCommand("input")]
public static void KeyboardInput([FromVm]GobVirtualMachine vm, string label, RawArg<string> value)
{
vm.Owner.Print(label, false, out var measure);
vm.Suspend();
vm.Owner.StartCoroutine(Wait());
IEnumerator Wait()
{
var input = "";
yield return null;
while (!Input.GetKeyDown(KeyCode.Return))
{
var nextInput = Input.inputString;
if (!string.IsNullOrEmpty(nextInput))
{
vm.Owner.Print(nextInput, false, out measure);
}
input += nextInput;
yield return null;
}
vm.Owner.cursor.x = 0;
vm.Owner.DropCursor(measure.y);
VmUtil.HandleValueString(vm, input, TypeCodes.STRING, value.state, value.address);
vm.Execute2(int.MaxValue);
}
}
Like I said, the INPUT command still isn't done, because technically, the 'label' arg is optional... I was considering just supporting method overloading, but I think that is going to be harder to achieve at the parsing layer for my system. Instead, I plan on just adding support for mid-arg optionals using something like the system interop `[Default]` attribute.
And once that is done, I plan to press on in the text book that is Hands On Dark Basic Pro, and just... implement....
EDIT:
oh, and like I said a few posts back, I'm NOT using Reflection to call these methods. I've created a C# Source Generator, such that when you compile those functions, I auto-matically generate a calling function that has a cached delegate pointer available for all the stages of compiling. The augo-gen'd code for one of those methods looks like this. Excuse the horrible formatting, this isn't meant to be human editable code. At some point, I want to see if I can support C# doc strings to automatically build up a help library from the target methods.
// method "text"
public static void Call_Text_voidR255009(VirtualMachine __vm)
{
// declare and assign all method inputs...
// handle text
VmUtil.ReadValueString(__vm, default, out var text,out var textVarState,out var textVarAddr);
// handle y
VmUtil.ReadValue<int>(__vm, default, out var y,out var yVarState,out var yVarAddr);
// handle x
VmUtil.ReadValue<int>(__vm, default, out var x,out var xVarState,out var xVarAddr);
// handle vm
var vm = (GobVirtualMachine)__vm;
// invoke the method Gosh Darn! Test it!
DarkBasicYo.Commands.DB_Core.DbTextCommands.Text(vm, x, y, text);
}