public void GenericOperationConverterTest() { OperationConverter gOpCnv = new OperationConverter(); NameValueContext c = new NameValueContext(); InvokeMethod op = new InvokeMethod(this, "TestMethod", new object[] { c }); TestGenOp genOp = new TestGenOp(); Assert.AreEqual(true, gOpCnv.CanConvert(genOp.GetType(), typeof(IOperation <object>))); Convert.ToString(gOpCnv.Convert(genOp, typeof(IOperation <object>)).GetType()); Assert.AreEqual(true, gOpCnv.Convert(genOp, typeof(IOperation <object>)) is IOperation <object>); ((IOperation <object>)gOpCnv.Convert(genOp, typeof(IOperation <object>))).Execute(c); Assert.AreEqual("a", c["b"]); Assert.AreEqual(true, gOpCnv.CanConvert(op.GetType(), typeof(IOperation <NameValueContext>))); Assert.AreEqual(true, gOpCnv.Convert(op, typeof(IOperation <NameValueContext>)) is IOperation <NameValueContext>); ((IOperation <NameValueContext>)gOpCnv.Convert(op, typeof(IOperation <NameValueContext>))).Execute(c); Assert.AreEqual("b", c["a"]); // compatible conversion between IOperation<> Assert.AreEqual(true, gOpCnv.CanConvert(typeof(IOperation <Context>), typeof(IOperation <NameValueContext>))); Assert.AreEqual(true, gOpCnv.CanConvert(typeof(IOperation <NameValueContext>), typeof(IOperation <Context>))); IOperation <NameValueExContext> genOpEx = (IOperation <NameValueExContext>)gOpCnv.Convert(genOp, typeof(IOperation <NameValueExContext>)); NameValueExContext cEx = new NameValueExContext(); genOpEx.Execute(cEx); Assert.AreEqual("a", cEx["b"]); }
private void ExportScene(Scene scene, string path) { StreamWriter writer = File.CreateText(path); var graphNodes = new List <GraphNode>(scene.Nodes.Count); for (int i = 0; i < scene.Nodes.Count; i++) { graphNodes.Add(new GraphNode()); } var maxId = graphNodes.Capacity + 1; for (int i = 0; i < scene.Nodes.Count; i++) { var node = scene.Nodes[i]; graphNodes[i].Node = node; switch (node) { case NodeJump nj: var targetId = scene.Nodes.IndexOf(nj.Target); graphNodes[i].Text = $"Jump from {i} to {targetId}"; graphNodes[i].NextNodes.Add(graphNodes[targetId], null); break; case NodeVariableJump nvj: graphNodes[i].Text = FormatVariableJump(nvj); var targetId1 = scene.Nodes.IndexOf(nvj.Target); var targetId2 = i + 1; if (nvj.Variable.Binary && nvj.Value == 0) { graphNodes[i].NextNodes.Add(graphNodes[targetId1], "false"); graphNodes[i].NextNodes.Add(graphNodes[targetId2], "true"); } else { graphNodes[i].NextNodes.Add(graphNodes[targetId1], "true"); graphNodes[i].NextNodes.Add(graphNodes[targetId2], "false"); } break; case NodeResponseDialogue nrd: graphNodes[i].Text = $"{nrd.Character.Name}\n\"{nrd.Dialogue.WordWrap(70)}\""; foreach (var response in nrd.ResponseMap) { var targetIdr = scene.Nodes.IndexOf(response.Target); if (graphNodes[i].NextNodes.ContainsKey(graphNodes[targetIdr])) { graphNodes[i].NextNodes[graphNodes[targetIdr]] += $"\n{response.Text}"; } else { graphNodes[i].NextNodes.Add(graphNodes[targetIdr], response.Text); } } break; case NodeCall nc: graphNodes[i].Text = nc.Target.Name; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeSetVariable nsv: graphNodes[i].Text = $"{nsv.Variable.Name} {_converter.Convert(nsv.Type, null, null, null)} {nsv.Value}"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeDialogue nd: graphNodes[i].Text = $"{nd.Character.Name}\n\"{nd.Text.WordWrap(70)}\""; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeMovement nm: graphNodes[i].Text = $"Movement\n{nm.Character.Name} {nm.MovementDirection} {nm.MovementType}"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeChangeBackground ncb: graphNodes[i].Text = $"Background change\n{ncb.Background.Name}"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeFadeMusic nfm: switch (nfm.AudioFadeType) { case NodeFadeMusic.AudioFadeTypeEnum.All: graphNodes[i].Text = $"Sound fade"; break; case NodeFadeMusic.AudioFadeTypeEnum.Music: graphNodes[i].Text = $"Music fade"; break; case NodeFadeMusic.AudioFadeTypeEnum.SFX: graphNodes[i].Text = $"SFX fade"; break; } graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeChangePose ncp: graphNodes[i].Text = $"Pose change\n{ncp.Character.Name} -> {ncp.Pose.Name}"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeWait nw: graphNodes[i].Text = $"Wait {nw.Duration}s"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodePlaySound nps: switch (nps.SoundType) { case NodePlaySound.SoundTypeEnum.Music: graphNodes[i].Text = $"Play music\n{nps.Sound.Name}"; break; case NodePlaySound.SoundTypeEnum.SFX: graphNodes[i].Text = $"Play sound\n{nps.Sound.Name}"; break; } graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; case NodeRet _: graphNodes[i].Text = "End\n" + scene.Name; break; case NodeTodo nt: graphNodes[i].Text = $"TODO\n{nt.Note}"; graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); break; default: graphNodes[i].Text = node.GetType().Name; if (i != scene.Nodes.Count - 1) { graphNodes[i].NextNodes.Add(graphNodes[i + 1], null); } break; } } foreach (var node in graphNodes) { foreach (var target in node.NextNodes) { var targetId = graphNodes.IndexOf(target.Key); if (targetId < 0) { continue; } graphNodes[targetId].PreviousNodes.Add(node); } } foreach (var node in graphNodes) { node.Critical = ShowEntireGraph || node.NextNodes.Count >= 2 || node.PreviousNodes.Count >= 2 || IsBranching(node.Node) || node.PreviousNodes.Any(n => IsBranching(n.Node)); var sign = IsCharacterVariable(node.Node); if (sign > 0) { node.Style = "style=filled fillcolor=green"; } else if (sign < 0) { node.Style = "style=filled fillcolor=red"; } } var startNode = graphNodes.First(); startNode.Critical = true; var endNode = graphNodes.Last(); endNode.Critical = true; endNode.Text = "End\n" + scene.Name; if (!ShowEntireGraph) { // Collapse 1-input, 1-output jump nodes foreach (var jumpNode in graphNodes.Where(n => n.Node is NodeJump)) { if (jumpNode.PreviousNodes.Count == 1 && jumpNode.NextNodes.Count == 1) { var prevNode = jumpNode.PreviousNodes.First(); var nextNode = jumpNode.NextNodes.First(); GraphNode nextCriticalNode = null; if (nextNode.Key.Critical) { nextCriticalNode = nextNode.Key; } else { nextCriticalNode = graphNodes[GetNextNode(graphNodes, nextNode.Key)]; } jumpNode.Critical = false; prevNode.Critical = true; var oldNext = prevNode.NextNodes[jumpNode]; prevNode.NextNodes.Remove(jumpNode); prevNode.NextNodes.Add(nextCriticalNode, oldNext); nextCriticalNode.PreviousNodes.Remove(jumpNode); nextCriticalNode.PreviousNodes.Add(prevNode); } } } writer.WriteLine("digraph {" + $"start [label=\"Start\n{scene.Name}\" shape=rarrow margin=0.2];\n" + "start -> 0;"); for (int i = 0; i < graphNodes.Count; i++) { GraphNode node = graphNodes[i]; if (!node.Critical) { continue; } if (node.Node is NodeRet) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"","\\\"")}\" shape=larrow margin=0.2];"); } else if (node.Node is NodeVariableJump) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\" shape=diamond];"); } else if (node.Node is NodeCall) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\" shape=rarrow];"); } else if (node.Node is NodeTodo) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\" shape=box color=green ];"); } else if (node.Node is NodeAchievement) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\" shape=box color=yellow ];"); } else if (node.Node is NodeLua) { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\\nPotential branch\" shape=box color=blue ];"); } else { writer.WriteLine($"{i} [label=\"{node.Text?.Replace("\"", "\\\"")}\" shape=box {node.Style}];"); } if (node.PreviousNodes.Count == 0 && i != 0) { if (scene.Nodes.Any(n => n is NodeLua)) { Debug.WriteLine($"Node with no incoming edges: {scene.Name} ({i}) - {node.Node} | But scene contains Lua"); } else { Debug.WriteLine($"Node with no incoming edges: {scene.Name} ({i}) - {node.Node}"); } } foreach (var nextNode in node.NextNodes) { if (node != graphNodes.Last()) { if (!nextNode.Key.Critical) { var skipNodeId = GetNextNode(graphNodes, node); writer.WriteLine($"{i} -> {skipNodeId};"); continue; } ; writer.WriteLine($"{i} -> {graphNodes.IndexOf(nextNode.Key)} [ label=\"{nextNode.Value?.Replace("\"", "\\\"")}\" ];"); } } if (node.NextNodes.Count >= 2) { writer.Write("{rank = same;"); foreach (var nextNode in node.NextNodes) { var nextNodeId = graphNodes.IndexOf(nextNode.Key); writer.Write($"{nextNodeId};"); } writer.WriteLine("}"); } } writer.WriteLine("}"); writer.Close(); }