FlowCanvas Forums › Support › Put runtime on .NET server
Hi there,
Would it be possible to extract the runtime portion of FlowCanvas and stick it on a .NET or Mono server? I know UnityEngine.dll is used extensively throughout the project but we’ve actually been able to link against UnityEngine.dll and use some of the simpler pieces (Mathf, Vector3, etc) successfully. From our limited research into this project, it seems like it’s not far off. Export to JSON is one of the big pieces you already have (as well as actually exposing the json library source).
Specifically, I’m asking if there might be a simple way to wrap the graph runtime with something other than a MonoBehaviour– this would allow us to write our own C# wrapper and run scripts on both client and server.
Hello,
I don’t think it’s possible to extract FlowCanvas to work outside of Unity without some heavy source code changes, since there are some tight dependencies with UnityEngine like you said. By the way, a FlowScript is a ScriptableObject, so basically it is wrapped in a ScriptableObject rather than a MonoBehaviour. The FlowScriptController component is just a convenience way of using a FlowScript. I suppose though you would even want to split the dependency from ScriptableObject as well, since that’s a UnityEngine class.
So, unfortunately I can’t think of a easy way to extract FlowCanvas and make it work completely outside of Unity at this point. :/
Join us on Discord: https://discord.gg/97q2Rjh
Thanks for the reply. Yah I see those dependencies, but I’m not giving up yet! It may take some work but FlowCanvas is an impressive enough piece of tech that making it work would be considerably less work than writing something from scratch.
My end goal is a FlowScript.dll which contains the serialization and runtime. This is so that I can run scripts on our .NET (or Mono) servers. On the client, I can just use the AssetStore package in full and run scripts as usual. Using the JSON serializer included I should be able to send scripts from server to client and execute them.
Step one: At this point, I’ve got a FlowCanvas VS project in which I’ve removed the UnityEditor portions (pretty much just deleting the Editor/ folders). I can build this dll successfully against only UnityEditor.dll. I haven’t gone through and deleted everything inside of #if UNITY_EDITOR, though I probably will want to at some point.
Step two: My next step is figuring out which pieces of Graph.cs requires ScriptableObject and refactor those dependencies away. I anticipate this to screw with quite a bit, but I think it’s doable.
Step three: Do the same with GraphOwner.cs. Essentially I would write my own host for graphs.
Step four: Write a common ScriptManager that can load and execute scripts. It loads them via JSON, then calls a factory to create a “ScriptHost”. The factory on the client creates FlowScriptControllers, the factory on the server creates my own bootstrapper.
Step five (bonus): I like the idea of the Blackboard network sync event. Basically, I’ll write a custom one for synchronization with our servers.
Wish me luck 🙂
Hey,
Thanks. I am really glad you like FlowCanvas.
It sounds like a big quest ahead of you! 🙂
Let me shed some light if I can:
1: Most core classes are partial, with parts of them being wrapped in if UNITY_EDITOR while the other part being the runtime one. Maybe this helps 🙂
2. Apart from the fact that ScriptableObject is used to save an asset file in unity, the Unity callbacks at the top (OnValidate, OnEnable etc) are very important to somehow be replicated.
3. Like you mention, GraphOwner is just a host and can easily be replicated. This is probably the easiest part to remove.
4. I think considering the previous parts are done, this will also be relatively easy (FC wise) by the use of the Deserialize method of the Graph.cs.
5. That would be a great bonus 🙂
Well Good luck! and let me know if I can help you in any way!
Thanks!
Join us on Discord: https://discord.gg/97q2Rjh
Thanks for the pointers. A few notes:
* Removed AgentTypeAttribute and GetFromAgentAttribute. These got pretty sticky and didn’t seem to be used anywhere.
* Lost property binding– this was very intermingled with GameObjects, so I removed it completely.
* Task.cs has a ton of unity dependencies as well, so I’m massaging those out (I hope).
Hey,
The two attributes mentioned are not essential part of FlowCanvas, but they could be used in custom code. So yeah, they can be removed for the goal of this.
Property bindings indeed are gameobject/component specific, thus best are to remove them like you did.
Task.cs and it’s subclasses ActionTask.cs and ConditionTask.cs are part of the NodeCanvas framework which FlowCanvas is built on. In FlowCanvas they are not essential, but allow executing actions/conditions made for NodeCanvas. In other words, Task, ActionTask and ConditionTask can be completely removed for the purpose of this case as well.
Join us on Discord: https://discord.gg/97q2Rjh
That makes sense, thanks for the heads up! It’s helpful to know I’m moving in the right direction.
One issue I’ve found and was able to bypass was that the JSON export saves assembly information for generic types. This is actually a C# thing, not a FlowCanvas thing. Type.FullName doesn’t return any assembly info unless it’s a generic type. Here’s an example:
FlowCanvas.Nodes.SimplexNodeWrapper'1[[FlowCanvas.Nodes.LogText, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]
The generic type doesn’t contain the assembly info, but the generic parameter does. This is an issue because as you can see, it makes the Assembly-CSharp.dll a requirement. Basically I made the serializer output this instead:
FlowCanvas.Nodes.SimplexNodeWrapper<FlowCanvas.Nodes.LogText>
To support this I added this bit to the beginning of ReflectionTools.GetType:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// handle generics if (typeName.Contains('<')) { var split = typeName.Split(new []{'<'}, 2); var genericParameterTypeNames = split[1].TrimEnd('>').Split(','); var genericTypeName = "{0}'{1}".FormatStr(split[0], genericParameterTypeNames.Length); var genericType = Type.GetType(genericTypeName); var genericParameterTypes = genericParameterTypeNames.Select<string, Type>(GetType); if (null == genericType) { return null; } return genericType.MakeGenericType(genericParameterTypes.ToArray()); } |
Now I can successfully deserialize the json on my server. Now I’m on to actually evaluating it.
So after a few more hours, I was able to get a simple flowscript running on a .NET server.
In testgraph.png you can see a simple graph that just logs on Awake. The second screenshot is my .NET server loading the modified json, deserializing into a Graph, then running the graph in a ServerGraphOwner. Obviously, it’s correctly logging “Awake.”
W00t! I assume I’ve broken quite a few other node types and events but I think I’ve completed the major pieces of getting this thing working outside of a Unity environment. Thanks for all the help! I’ll continue posting if I have any more updates.
That’s super great to know you’ve gone that far! Nice 🙂
I’m now really wondering what works and what does not, considering the changes you’ve made and looking forward to more updates on your quest, interesting as it is!
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
More progress!
* I had to refactor MonoManager, implementing StartCoroutine myself and calling all the update methods from the server loop.
* Removing some dependencies on UnityEngine.Random and Time.time in many nodes.
* Reworking EventRouter a tad. Basically, I’ve thrown away Send Event and will only use Send Global Event.
I was able to get all of this running nicely on the server.
Next steps: I’m now working on extracting the different pieces into different projects for better code reuse. I have four projects: common, client, editor, and server. So far common pretty much has only serialization. I would eventually like to move graph in there as soon as I can abstract away more of the Unity logic.
Much of the meat of this is simply deleting “: MonoBehaviour” to see why it needs a MonoBehaviour. I am adding interfaces to fill in the necessary details. Piece by piece I am able to move pieces to common, client, or editor, while implementing the necessary leftovers for server.
It’s arduous, but I’m getting more and more functionality.
Thanks a lot for the updates!
Sounds nice thus far and it seems you are achieving what you sought to 🙂
Join us on Discord: https://discord.gg/97q2Rjh