Example #1
0
        void Emit(Node node, ByteCode code, object operandA = null, object operandB = null)
        {
            var instruction = new Instruction();
            instruction.operation = code;
            instruction.operandA = operandA;
            instruction.operandB = operandB;

            node.instructions.Add (instruction);

            if (code == ByteCode.Label) {
                // Add this label to the label table
                node.labels.Add ((string)instruction.operandA, node.instructions.Count - 1);
            }
        }
        internal void RunInstruction(Instruction i)
        {
            switch (i.operation) {
            case ByteCode.Label:

                // No-op; used as a destination for JumpTo and Jump.
                break;
            case ByteCode.JumpTo:

                // Jumps to a named label
                state.programCounter = FindInstructionPointForLabel ((string)i.operandA);

                break;
            case ByteCode.RunLine:

                // Looks up a string from the string table and
                // passes it to the client as a line

                var lineText = program.GetString ((string)i.operandA);

                lineHandler (new Dialogue.LineResult (lineText));

                break;
            case ByteCode.RunCommand:

                // Passes a string to the client as a custom command
                commandHandler (
                    new Dialogue.CommandResult ((string)i.operandA)
                );

                break;
            case ByteCode.PushString:

                // Pushes a string value onto the stack; the operand
                // is an index into the string table, so that's looked up
                // first.
                state.PushValue (program.GetString ((string)i.operandA));

                break;
            case ByteCode.PushNumber:

                // Pushes a number onto the stack.
                state.PushValue (Convert.ToSingle(i.operandA));

                break;
            case ByteCode.PushBool:

                // Pushes a boolean value onto the stack.
                state.PushValue (Convert.ToBoolean(i.operandA));

                break;
            case ByteCode.PushNull:

                // Pushes a null value onto the stack.
                state.PushValue (Value.NULL);

                break;
            case ByteCode.JumpIfFalse:

                // Jumps to a named label if the value on the top of the stack
                // evaluates to the boolean value 'false'.
                if (state.PeekValue ().AsBool == false) {
                    state.programCounter = FindInstructionPointForLabel ((string)i.operandA);
                }
                break;

            case ByteCode.Jump:

                // Jumps to a label whose name is on the stack.
                var jumpDestination = state.PeekValue ().AsString;
                state.programCounter = FindInstructionPointForLabel (jumpDestination);

                break;

            case ByteCode.Pop:

                // Pops a value from the stack.
                state.PopValue ();
                break;

            case ByteCode.CallFunc:

                // Call a function, whose parameters are expected to
                // be on the stack. Pushes the function's return value,
                // if it returns one.
                var functionName = (string)i.operandA;

                var function = dialogue.library.GetFunction (functionName);
                {

                    var paramCount = function.paramCount;

                    // If this function takes "-1" parameters, it is variadic.
                    // Expect the compiler to have placed the number of parameters
                    // actually passed at the top of the stack.
                    if (paramCount == -1) {
                        paramCount = (int)state.PopValue ().AsNumber;
                    }

                    // Get the parameters, which were pushed in reverse
                    Value[] parameters = new Value[paramCount];
                    for (int param = paramCount - 1; param >= 0; param--) {
                        parameters [param] = state.PopValue ();
                    }

                    // Invoke the function
                    var result = function.InvokeWithArray (parameters);

                    // If the function returns a value, push it
                    if (function.returnsValue) {
                        state.PushValue (result);
                    }
                }

                break;
            case ByteCode.PushVariable:

                // Get the contents of a variable, push that onto the stack.
                var variableName = (string)i.operandA;
                var loadedValue = dialogue.continuity.GetValue (variableName);
                state.PushValue (loadedValue);

                break;
            case ByteCode.StoreVariable:

                // Store the top value on the stack in a variable.
                var topValue = state.PeekValue ();
                var destinationVariableName = (string)i.operandA;
                dialogue.continuity.SetValue (destinationVariableName, topValue);

                break;
            case ByteCode.Stop:

                // Immediately stop execution, and report that fact.
                nodeCompleteHandler (new Dialogue.NodeCompleteResult (null));
                executionState = ExecutionState.Stopped;

                break;
            case ByteCode.RunNode:

                string nodeName;

                if (string.IsNullOrEmpty((string) i.operandA)) {
                    // Get a string from the stack, and jump to a node with that name.
                     nodeName = state.PeekValue ().AsString;
                } else {
                    // jump straight to the node
                    nodeName = (string)i.operandA;
                }

                nodeCompleteHandler (new Dialogue.NodeCompleteResult (nodeName));
                SetNode (nodeName);

                break;
            case ByteCode.AddOption:

                // Add an option to the current state.
                state.currentOptions.Add (new KeyValuePair<string, string> ((string)i.operandA, (string)i.operandB));

                break;
            case ByteCode.ShowOptions:

                // If we have no options to show, immediately stop.
                if (state.currentOptions.Count == 0) {
                    executionState = ExecutionState.Stopped;
                    nodeCompleteHandler (new Dialogue.NodeCompleteResult (null));
                    break;
                }

                // If we have a single option, and it has no label, select it immediately and continue
                // execution
                if (state.currentOptions.Count == 1 && state.currentOptions[0].Key == null) {
                    var destinationNode = state.currentOptions[0].Value;
                    state.PushValue(destinationNode);
                    state.currentOptions.Clear();
                    break;
                }

                if (dialogue.continuity.GetValue(SpecialVariables.ShuffleOptions).boolValue) {
                    // Shuffle the dialog options if needed
                    var n = state.currentOptions.Count;
                    for (int opt1 = 0; opt1 < n; opt1++) {
                        int opt2 = opt1 + (int)(random.NextDouble () * (n - opt1)); // r.Next(0, state.currentOptions.Count-1);
                        var temp = state.currentOptions [opt2];
                        state.currentOptions [opt2] = state.currentOptions [opt1];
                        state.currentOptions [opt1] = temp;
                    }
                }

                // Otherwise, present the list of options to the user and let them pick
                var optionStrings = new List<string> ();

                foreach (var option in state.currentOptions) {
                    optionStrings.Add (program.GetString (option.Key));
                }

                // We can't continue until our client tell us which option to pick
                executionState = ExecutionState.WaitingOnOptionSelection;

                // Pass the options set to the client, as well as a delegate for them to call when the
                // user has made a selection
                optionsHandler (new Dialogue.OptionSetResult (optionStrings, delegate (int selectedOption) {

                    // we now know what number option was selected; push the corresponding node name
                    // to the stack
                    var destinationNode = state.currentOptions[selectedOption].Value;
                    state.PushValue(destinationNode);

                    // We no longer need the accumulated list of options; clear it so that it's
                    // ready for the next one
                    state.currentOptions.Clear();

                    // We can now also keep running
                    executionState = ExecutionState.Running;

                }));

                break;
            default:

                // Whoa, no idea what bytecode this is. Stop the program
                // and throw an exception.
                executionState = ExecutionState.Stopped;
                throw new ArgumentOutOfRangeException ();
            }
        }