├── .github └── FUNDING.yml ├── .gitignore ├── Editor.meta ├── Editor ├── Editmode.meta ├── Editmode │ ├── SleipnirGraphViewEditmode.cs │ ├── SleipnirGraphViewEditmode.cs.meta │ ├── SleipnirNodeViewEditmode.cs │ └── SleipnirNodeViewEditmode.cs.meta ├── GraphAssetEditor.cs ├── GraphAssetEditor.cs.meta ├── GraphAssetExtensions.cs ├── GraphAssetExtensions.cs.meta ├── GraphNodePropertyProcessor.cs ├── GraphNodePropertyProcessor.cs.meta ├── NodeViews.meta ├── NodeViews │ ├── GraphReferenceNodeView.cs │ └── GraphReferenceNodeView.cs.meta ├── Playmode.meta ├── Playmode │ ├── SleipnirGraphViewPlaymode.cs │ ├── SleipnirGraphViewPlaymode.cs.meta │ ├── SleipnirNodeViewPlaymode.cs │ └── SleipnirNodeViewPlaymode.cs.meta ├── RedOwl.Sleipnir.Editor.asmdef ├── RedOwl.Sleipnir.Editor.asmdef.meta ├── SleipnirCodeGeneration.cs ├── SleipnirCodeGeneration.cs.meta ├── SleipnirEditor.cs ├── SleipnirEditor.cs.meta ├── SleipnirGraphEdgeConnectorListener.cs ├── SleipnirGraphEdgeConnectorListener.cs.meta ├── SleipnirGraphSearchProvider.cs ├── SleipnirGraphSearchProvider.cs.meta ├── SleipnirGraphViewBase.cs ├── SleipnirGraphViewBase.cs.meta ├── SleipnirNodeViewBase.cs ├── SleipnirNodeViewBase.cs.meta ├── SleipnirPortView.cs ├── SleipnirPortView.cs.meta ├── SleipnirWindow.cs └── SleipnirWindow.cs.meta ├── Engine.meta ├── Engine ├── Flow.cs ├── Flow.cs.meta ├── Graph.meta ├── Graph │ ├── Connection.cs │ ├── Connection.cs.meta │ ├── Graph.cs │ ├── Graph.cs.meta │ ├── GraphAsset.cs │ ├── GraphAsset.cs.meta │ ├── GraphAssetObject.cs │ ├── GraphAssetObject.cs.meta │ ├── GraphAttribute.cs │ ├── GraphAttribute.cs.meta │ ├── GraphController.cs │ ├── GraphController.cs.meta │ ├── GraphView.cs │ ├── GraphView.cs.meta │ ├── ShowInNode.cs │ ├── ShowInNode.cs.meta │ ├── Types.meta │ └── Types │ │ ├── FlowGraph.cs │ │ └── FlowGraph.cs.meta ├── Helpers.meta ├── Helpers │ ├── BetterCollection.cs │ ├── BetterCollection.cs.meta │ ├── BetterDictionary.cs │ ├── BetterDictionary.cs.meta │ ├── TypeCache.cs │ ├── TypeCache.cs.meta │ ├── TypeExtensions.cs │ └── TypeExtensions.cs.meta ├── Node.meta ├── Node │ ├── Node.cs │ ├── Node.cs.meta │ ├── NodeAttribute.cs │ ├── NodeAttribute.cs.meta │ ├── NodeView.cs │ ├── NodeView.cs.meta │ ├── NodeViewAttribute.cs │ ├── NodeViewAttribute.cs.meta │ ├── RequireNodeAttribute.cs │ ├── RequireNodeAttribute.cs.meta │ ├── Types.meta │ └── Types │ │ ├── Common.meta │ │ ├── Common │ │ ├── KitchenSinkNode.cs │ │ ├── KitchenSinkNode.cs.meta │ │ ├── LogNode.cs │ │ ├── LogNode.cs.meta │ │ ├── ValueNodes.cs │ │ └── ValueNodes.cs.meta │ │ ├── FlowNodes.meta │ │ ├── FlowNodes │ │ ├── BranchNode.cs │ │ ├── BranchNode.cs.meta │ │ ├── CoroutineNode.cs │ │ ├── CoroutineNode.cs.meta │ │ ├── EnterNode.cs │ │ ├── EnterNode.cs.meta │ │ ├── ExitNode.cs │ │ ├── ExitNode.cs.meta │ │ ├── FixedUpdateNode.cs │ │ ├── FixedUpdateNode.cs.meta │ │ ├── LateUpdateNode.cs │ │ ├── LateUpdateNode.cs.meta │ │ ├── LoopNode.cs │ │ ├── LoopNode.cs.meta │ │ ├── StartNode.cs │ │ ├── StartNode.cs.meta │ │ ├── TypeWriterNode.cs │ │ ├── TypeWriterNode.cs.meta │ │ ├── UpdateNode.cs │ │ ├── UpdateNode.cs.meta │ │ ├── WhileNode.cs │ │ └── WhileNode.cs.meta │ │ ├── MathNodes.meta │ │ ├── MathNodes │ │ ├── AndNode.cs │ │ ├── AndNode.cs.meta │ │ ├── ComparisonNode.cs │ │ ├── ComparisonNode.cs.meta │ │ ├── MathNode.cs │ │ ├── MathNode.cs.meta │ │ ├── NotNode.cs │ │ ├── NotNode.cs.meta │ │ ├── OperationNode.cs │ │ ├── OperationNode.cs.meta │ │ ├── OrNode.cs │ │ ├── OrNode.cs.meta │ │ ├── SignNode.cs │ │ └── SignNode.cs.meta │ │ ├── StateMachine.meta │ │ ├── StateMachine │ │ ├── StateNode.cs │ │ └── StateNode.cs.meta │ │ ├── SubGraph.meta │ │ └── SubGraph │ │ ├── GraphFlowPortNode.cs │ │ ├── GraphFlowPortNode.cs.meta │ │ ├── GraphReferenceNode.cs │ │ ├── GraphReferenceNode.cs.meta │ │ ├── GraphValuePortNode.cs │ │ └── GraphValuePortNode.cs.meta ├── Port.meta ├── Port │ ├── FlowPort.cs │ ├── FlowPort.cs.meta │ ├── FlowPortAttribute.cs │ ├── FlowPortAttribute.cs.meta │ ├── Port.cs │ ├── Port.cs.meta │ ├── PortAttribute.cs │ ├── PortAttribute.cs.meta │ ├── PortCapacity.cs │ ├── PortCapacity.cs.meta │ ├── PortDirection.cs │ ├── PortDirection.cs.meta │ ├── PortId.cs │ ├── PortId.cs.meta │ ├── ValuePort.cs │ ├── ValuePort.cs.meta │ ├── ValuePortAttribute.cs │ └── ValuePortAttribute.cs.meta ├── RedOwl.Sleipnir.Engine.asmdef └── RedOwl.Sleipnir.Engine.asmdef.meta ├── README.md ├── README.md.meta ├── Resources.meta ├── Resources ├── SleipnirWindow.uss └── SleipnirWindow.uss.meta ├── example.png ├── example.png.meta ├── package.json └── package.json.meta /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | patreon: redowlgames 3 | ko_fi: redowlgames 4 | custom: ['buymeacoffee.com/redowlgames'] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b4e8d5a9731a74742aed6ae5da7d358e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Editmode.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 673f19ed6ee2404f929eaa523c92d624 3 | timeCreated: 1609796159 -------------------------------------------------------------------------------- /Editor/Editmode/SleipnirGraphViewEditmode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RedOwl.Sleipnir.Engine; 4 | using UnityEditor; 5 | using UnityEditor.Experimental.GraphView; 6 | using UnityEngine; 7 | using UnityEngine.UIElements; 8 | using NodeView = UnityEditor.Experimental.GraphView.Node; 9 | using PortView = UnityEditor.Experimental.GraphView.Port; 10 | 11 | namespace RedOwl.Sleipnir.Editor 12 | { 13 | public class SleipnirGraphViewEditmode : SleipnirGraphViewBase, IGraphView 14 | { 15 | public SleipnirGraphViewEditmode() 16 | { 17 | RegisterCallback(GeometryChangedCallback); 18 | RegisterCallback(OnKeyUp); 19 | 20 | graphViewChanged = OnGraphViewChanged; 21 | viewTransformChanged = OnViewTransformChanged; 22 | 23 | SetupZoom(ContentZoomer.DefaultMinScale * 0.5f, ContentZoomer.DefaultMaxScale); 24 | 25 | this.AddManipulator(new ContentDragger()); 26 | this.AddManipulator(new SelectionDragger()); 27 | this.AddManipulator(new RectangleSelector()); 28 | 29 | CreateGridBackground(); 30 | CreateMiniMap(); 31 | } 32 | 33 | public override List GetCompatiblePorts(PortView startPort, NodeAdapter nodeAdapter) 34 | { 35 | var compatible = new List(); 36 | ports.ForEach(p => 37 | { 38 | if (startPort == p) return; 39 | if (startPort.node == p.node) return; 40 | if (startPort.direction == p.direction) return; 41 | if (p.direction == Direction.Input && !startPort.portType.IsCastableTo(p.portType, true)) return; 42 | if (p.direction == Direction.Output && !p.portType.IsCastableTo(startPort.portType, true)) return; 43 | compatible.Add(p); 44 | }); 45 | return compatible; 46 | } 47 | 48 | public override void Save() 49 | { 50 | if (GraphAsset is ScriptableObject so && so != null) 51 | { 52 | // TODO: Purge Keys from connections tables that have no values in their port collection? 53 | EditorUtility.SetDirty(so); 54 | AssetDatabase.SaveAssets(); 55 | } 56 | } 57 | 58 | public void Load(IGraphAsset asset) 59 | { 60 | if (asset == null) return; 61 | GraphAsset = asset; 62 | Reload(); 63 | } 64 | 65 | public void Reload() 66 | { 67 | Cleanup(); 68 | if (GraphAsset == null) return; 69 | Graph.Definition(Graph); 70 | _nodeViewCache = new Dictionary(Graph.NodeCount); 71 | CreateSearch(); 72 | // TODO: If graph.NodeCount > 100 we need a loading bar and maybe an async process that does the below 73 | // https://docs.unity3d.com/ScriptReference/EditorUtility.DisplayProgressBar.html 74 | CreateNodeViews(); 75 | CreateConnections(); 76 | } 77 | 78 | protected void Cleanup() 79 | { 80 | graphViewChanged = null; 81 | DeleteElements(graphElements.ToList()); 82 | graphViewChanged = OnGraphViewChanged; 83 | } 84 | 85 | private void CreateNodeViews() 86 | { 87 | foreach (var node in Graph.Nodes) 88 | { 89 | CreateNodeView(node); 90 | } 91 | } 92 | 93 | private void CreateConnections() 94 | { 95 | foreach (var node in Graph.Nodes) 96 | { 97 | var view = _nodeViewCache[node.NodeId]; 98 | CreateValueConnections(view, node); 99 | CreateFlowConnections(view, node); 100 | } 101 | } 102 | 103 | private void CreateValueConnections(INodeView view, INode node) 104 | { 105 | foreach (var valueIn in node.ValueInPorts.Values) 106 | { 107 | foreach (var connection in Graph.ValueInConnections.SafeGet(valueIn.Id)) 108 | { 109 | if (!_nodeViewCache.TryGetValue(connection.Node, out var outputView)) continue; 110 | var inputPort = view.ValueInPortContainer.Q(valueIn.Id.Port); 111 | var outputPort = outputView.ValueOutPortContainer.Q(connection.Port); 112 | if (inputPort != null && outputPort != null) 113 | AddElement(outputPort.ConnectTo(inputPort)); 114 | } 115 | } 116 | } 117 | 118 | private void CreateFlowConnections(INodeView view, INode flowNode) 119 | { 120 | foreach (var flowOut in flowNode.FlowOutPorts.Values) 121 | { 122 | foreach (var connection in Graph.FlowOutConnections.SafeGet(flowOut.Id)) 123 | { 124 | if (!_nodeViewCache.TryGetValue(connection.Node, out var inputView)) 125 | { 126 | Debug.Log($"Unable To Find Node View for {connection.Node}"); 127 | continue; 128 | } 129 | var inputPort = inputView.FlowInPortContainer.Q(connection.Port); 130 | var outputPort = view.FlowOutPortContainer.Q(flowOut.Id.Port); 131 | if (inputPort != null && outputPort != null) 132 | AddElement(outputPort.ConnectTo(inputPort)); 133 | else 134 | Debug.Log($"Unable To Make a Flow Port Connection | {flowOut} => {connection.Node}.{connection.Port}"); 135 | } 136 | } 137 | } 138 | 139 | private void GeometryChangedCallback(GeometryChangedEvent evt) 140 | { 141 | MiniMap.SetPosition(new Rect(worldBound.width - 205, 25, 200, 100)); 142 | } 143 | 144 | private void CleanupFlowConnectionElements(PortView port) 145 | { 146 | foreach (Edge connection in new List(port.connections)) 147 | { 148 | if ((connection.capabilities & Capabilities.Deletable) != 0) 149 | { 150 | Graph.Disconnect((IPort) connection.output.userData, (IPort) connection.input.userData); 151 | // Replicate what Unity is doing in their "DeleteElement" method 152 | connection.output.Disconnect(connection); 153 | connection.input.Disconnect(connection); 154 | // connection.output = null; 155 | // connection.input = null; 156 | RemoveElement(connection); 157 | } 158 | } 159 | } 160 | private GraphViewChange OnGraphViewChanged(GraphViewChange change) 161 | { 162 | RecordUndo("Graph Edit"); 163 | bool changeMade = false; 164 | if (change.elementsToRemove != null) 165 | { 166 | foreach (var element in change.elementsToRemove) 167 | { 168 | switch (element) 169 | { 170 | case INodeView view: 171 | changeMade = true; 172 | view.FlowInPortContainer.Query().ForEach(CleanupFlowConnectionElements); 173 | view.FlowOutPortContainer.Query().ForEach(CleanupFlowConnectionElements); 174 | _nodeViewCache.Remove(view.Node.NodeId); 175 | Graph.Remove(view.Node); 176 | break; 177 | case Edge edge: 178 | changeMade = true; 179 | Graph.Disconnect((IPort)edge.output.userData, (IPort)edge.input.userData); 180 | break; 181 | default: 182 | Debug.LogWarning($"Unhandeled GraphElement Removed: {element.GetType().FullName} | {element.name} | {element.title}"); 183 | break; 184 | } 185 | } 186 | } 187 | 188 | if (change.movedElements != null) 189 | { 190 | foreach (var element in change.movedElements) 191 | { 192 | switch (element) 193 | { 194 | case INodeView view: 195 | changeMade = true; 196 | view.Node.NodeRect = view.GetPosition(); 197 | break; 198 | default: 199 | Debug.LogWarning($"Unhandeled GraphElement Moved: {element.GetType().FullName} | {element.name} | {element.title}"); 200 | break; 201 | } 202 | } 203 | } 204 | 205 | if (change.edgesToCreate != null) 206 | { 207 | foreach (var edge in change.edgesToCreate) 208 | { 209 | changeMade = true; 210 | Graph.Connect((IPort)edge.output.userData, (IPort)edge.input.userData); 211 | } 212 | } 213 | 214 | if (changeMade) 215 | { 216 | Save(); 217 | Graph.MarkDirty(); 218 | } 219 | 220 | return change; 221 | } 222 | 223 | private void OnViewTransformChanged(GraphView graphview) 224 | { 225 | 226 | } 227 | 228 | private void OnKeyUp(KeyUpEvent evt) 229 | { 230 | if (evt.target != this) 231 | { 232 | return; 233 | } 234 | 235 | switch (evt.keyCode) 236 | { 237 | case KeyCode.C when !evt.ctrlKey && !evt.commandKey: 238 | // AddComment(); 239 | break; 240 | case KeyCode.M: 241 | MiniMap.visible = !MiniMap.visible; 242 | break; 243 | case KeyCode.H when !evt.ctrlKey && !evt.commandKey: 244 | HorizontallyAlignSelectedNodes(); 245 | break; 246 | case KeyCode.V when !evt.ctrlKey && !evt.commandKey: 247 | VerticallyAlignSelectedNodes(); 248 | break; 249 | } 250 | } 251 | 252 | protected void HorizontallyAlignSelectedNodes() 253 | { 254 | float sum = 0; 255 | int count = 0; 256 | 257 | // TODO: Implement a way to Align to "first selected" thing rather then average 258 | foreach (var selectable in selection) 259 | { 260 | if (selectable is NodeView node) 261 | { 262 | sum += node.GetPosition().xMin; 263 | count++; 264 | } 265 | } 266 | 267 | float xAvg = sum / count; 268 | foreach (var selectable in selection) 269 | { 270 | if (selectable is INodeView node && node.IsMoveable) 271 | { 272 | var pos = node.GetPosition(); 273 | pos.xMin = xAvg; 274 | node.SetPosition(pos); 275 | } 276 | } 277 | } 278 | 279 | 280 | protected void VerticallyAlignSelectedNodes() 281 | { 282 | float sum = 0; 283 | int count = 0; 284 | 285 | foreach (var selectable in selection) 286 | { 287 | if (selectable is INodeView node && node.IsMoveable) 288 | { 289 | sum += node.GetPosition().yMin; 290 | count++; 291 | } 292 | } 293 | 294 | float yAvg = sum / count; 295 | foreach (var selectable in selection) 296 | { 297 | if (selectable is NodeView node) 298 | { 299 | var pos = node.GetPosition(); 300 | pos.yMin = yAvg; 301 | node.SetPosition(pos); 302 | } 303 | } 304 | } 305 | } 306 | } -------------------------------------------------------------------------------- /Editor/Editmode/SleipnirGraphViewEditmode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d8d14c5d489b462890342db8663cf678 3 | timeCreated: 1606406399 -------------------------------------------------------------------------------- /Editor/Editmode/SleipnirNodeViewEditmode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Editor 2 | { 3 | public class SleipnirNodeViewEditmode : SleipnirNodeViewBase 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Editor/Editmode/SleipnirNodeViewEditmode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5410918223e48d4a15e3f9f58ce9a8f 3 | timeCreated: 1609796190 -------------------------------------------------------------------------------- /Editor/GraphAssetEditor.cs: -------------------------------------------------------------------------------- 1 | #if !ODIN_INSPECTOR 2 | using RedOwl.Sleipnir.Engine; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Editor 7 | { 8 | [CustomEditor(typeof(GraphAssetObject), true)] 9 | public class GraphAssetEditor : UnityEditor.Editor 10 | { 11 | bool expanded = true; 12 | 13 | public override void OnInspectorGUI () 14 | { 15 | // My God Thank you Odin for making this much easier ... /facepalm 16 | if (GUILayout.Button("Open Editor", GUILayout.Height(40))) SleipnirEditor.Open((GraphAsset) serializedObject.targetObject); 17 | EditorGUI.BeginChangeCheck(); 18 | serializedObject.UpdateIfRequiredOrScript(); 19 | 20 | expanded = EditorGUILayout.BeginFoldoutHeaderGroup(expanded, "Graph Settings"); 21 | --EditorGUI.indentLevel; 22 | SerializedProperty iterator = serializedObject.GetIterator(); 23 | for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) 24 | { 25 | if (iterator.name == "_graph") EditorGUILayout.PropertyField(iterator, GUIContent.none, true); 26 | 27 | } 28 | ++EditorGUI.indentLevel; 29 | EditorGUILayout.EndFoldoutHeaderGroup(); 30 | serializedObject.ApplyModifiedProperties(); 31 | EditorGUI.EndChangeCheck(); 32 | } 33 | } 34 | } 35 | #else 36 | // TODO: Have Odin add a custom button for SleipnirEditor.Open(GraphAsset) 37 | #endif -------------------------------------------------------------------------------- /Editor/GraphAssetEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5128bedc636c4472aa995aec37d3fcad 3 | timeCreated: 1607658500 -------------------------------------------------------------------------------- /Editor/GraphAssetExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RedOwl.Sleipnir.Engine; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Editor 7 | { 8 | public static class GraphAssetExtensions 9 | { 10 | public static void Save(this IGraph graph, string relativeFolder = null, string name = null) 11 | { 12 | string EnsureFolderExits(string path) 13 | { 14 | string current = "Assets"; 15 | foreach (string folder in path.Replace("\\", "/").Split('/')) 16 | { 17 | string next = $"{current}/{folder}"; 18 | if (!AssetDatabase.IsValidFolder(next)) 19 | { 20 | Debug.Log($"Creating Folder: '{next}'"); 21 | AssetDatabase.CreateFolder(current, folder); 22 | } 23 | current = next; 24 | } 25 | 26 | return current; 27 | } 28 | var asset = ScriptableObject.CreateInstance(); 29 | asset.Graph = graph; 30 | name = string.IsNullOrEmpty(name) ? graph.GetType().Name : name; 31 | relativeFolder = string.IsNullOrEmpty(relativeFolder) ? "Resources" : relativeFolder; 32 | string filepath = Path.Combine(EnsureFolderExits(relativeFolder), name); 33 | Debug.Log($"Saving Graph Asset @ '{filepath}.asset'"); 34 | AssetDatabase.CreateAsset(asset, $"{filepath}.asset"); 35 | AssetDatabase.SaveAssets(); 36 | AssetDatabase.Refresh(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Editor/GraphAssetExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 678099baead34a54a528715c2fce337b 3 | timeCreated: 1610890455 -------------------------------------------------------------------------------- /Editor/GraphNodePropertyProcessor.cs: -------------------------------------------------------------------------------- 1 | #if ODIN_INSPECTOR 2 | using System.Collections.Generic; 3 | using RedOwl.Sleipnir.Engine; 4 | using Sirenix.OdinInspector.Editor; 5 | 6 | namespace RedOwl.Sleipnir.Editor 7 | { 8 | public class GraphNodePropertyProcessor : OdinPropertyProcessor where T : INode 9 | { 10 | public override void ProcessMemberProperties(List propertyInfos) 11 | { 12 | var match = typeof(Port); 13 | for (int i = propertyInfos.Count - 1; i >= 0; i--) 14 | { 15 | var info = propertyInfos[i]; 16 | if (match.IsAssignableFrom(info.TypeOfValue)) propertyInfos.RemoveAt(i); 17 | } 18 | } 19 | } 20 | } 21 | 22 | #endif -------------------------------------------------------------------------------- /Editor/GraphNodePropertyProcessor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a42832fa07ca4378b21d6092be46c9ea 3 | timeCreated: 1607227624 -------------------------------------------------------------------------------- /Editor/NodeViews.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c54ec23eaebb4421a761f3eb7cb5149b 3 | timeCreated: 1609827279 -------------------------------------------------------------------------------- /Editor/NodeViews/GraphReferenceNodeView.cs: -------------------------------------------------------------------------------- 1 | using RedOwl.Sleipnir.Engine; 2 | using UnityEngine; 3 | using UnityEngine.UIElements; 4 | 5 | namespace RedOwl.Sleipnir.Editor 6 | { 7 | [NodeView(typeof(Graph))] 8 | [NodeView(typeof(GraphReferenceNode))] 9 | public class GraphReferenceNodeView : SleipnirNodeViewBase 10 | { 11 | private Button OpenGraphButton; 12 | 13 | protected override void OnInitialize() 14 | { 15 | base.OnInitialize(); 16 | OpenGraphButton = new Button(OnClickOpenGraph) {text = "Open"}; 17 | titleButtonContainer.Add(OpenGraphButton); 18 | } 19 | 20 | private void OnClickOpenGraph() 21 | { 22 | switch (Node) 23 | { 24 | case GraphReferenceNode graphReference: 25 | SleipnirEditor.Open(graphReference.Asset, true); 26 | break; 27 | case Graph graph: 28 | var asset = ScriptableObject.CreateInstance(); 29 | asset.name = "SubGraph"; 30 | asset.Graph = graph; 31 | SleipnirEditor.Open(asset, true); 32 | break; 33 | } 34 | 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Editor/NodeViews/GraphReferenceNodeView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed2c5a99f150489e96618ab529e0ab26 3 | timeCreated: 1609790311 -------------------------------------------------------------------------------- /Editor/Playmode.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3216d8beb81d4dd08eaf629588932481 3 | timeCreated: 1609796146 -------------------------------------------------------------------------------- /Editor/Playmode/SleipnirGraphViewPlaymode.cs: -------------------------------------------------------------------------------- 1 | using RedOwl.Sleipnir.Engine; 2 | 3 | namespace RedOwl.Sleipnir.Editor 4 | { 5 | public class SleipnirGraphViewPlaymode : SleipnirGraphViewBase, IGraphView 6 | { 7 | public void Load(IGraphAsset asset) 8 | { 9 | CreateGridBackground(); 10 | } 11 | 12 | public void Reload() {} 13 | 14 | public override void Save() {} 15 | } 16 | } -------------------------------------------------------------------------------- /Editor/Playmode/SleipnirGraphViewPlaymode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 091abc90a1404e3582be2002eb66d49d 3 | timeCreated: 1609795957 -------------------------------------------------------------------------------- /Editor/Playmode/SleipnirNodeViewPlaymode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Editor 2 | { 3 | public class SleipnirNodeViewPlaymode : SleipnirNodeViewBase 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Editor/Playmode/SleipnirNodeViewPlaymode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07482c8cfed142bc9680d9464ee6c257 3 | timeCreated: 1609796223 -------------------------------------------------------------------------------- /Editor/RedOwl.Sleipnir.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RedOwl.Sleipnir.Editor", 3 | "references": [ 4 | "RedOwl.Sleipnir.Engine" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [] 16 | } -------------------------------------------------------------------------------- /Editor/RedOwl.Sleipnir.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 532c6bdc37368c04ebee8a3128d43214 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/SleipnirCodeGeneration.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Editor 2 | { 3 | public class SleipnirCodeGeneration 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Editor/SleipnirCodeGeneration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a96bc61d5e57477484e30eddd4d5052b 3 | timeCreated: 1610654376 -------------------------------------------------------------------------------- /Editor/SleipnirEditor.cs: -------------------------------------------------------------------------------- 1 | using RedOwl.Sleipnir.Engine; 2 | using UnityEditor; 3 | using UnityEditor.Callbacks; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Editor 7 | { 8 | public static class SleipnirEditor 9 | { 10 | private const string DialogTitle = "Open In New Window?"; 11 | private const string DialogMessage = "Do you want to load this graph into the existing editor window?"; 12 | 13 | [OnOpenAsset(0)] 14 | public static bool OnOpenAsset(int instanceID, int line) 15 | { 16 | if (!(EditorUtility.InstanceIDToObject(instanceID) is IGraphAsset asset)) return false; 17 | GetOrCreate().Load(asset); 18 | return true; 19 | } 20 | 21 | public static void Open(IGraphAsset asset, bool nested = false) 22 | { 23 | GetOrCreate().Load(asset, nested); 24 | } 25 | 26 | // private static bool WindowIsOpenAlready() 27 | // { 28 | // Object[] objectsOfTypeAll = Resources.FindObjectsOfTypeAll(typeof(SleipnirWindow)); 29 | // EditorWindow editorWindow = objectsOfTypeAll.Length != 0 ? (EditorWindow) objectsOfTypeAll[0] : null; 30 | // return (bool) editorWindow; 31 | // } 32 | 33 | public static SleipnirWindow GetOrCreate() 34 | { 35 | // This was Quite Annoying 36 | // SleipnirWindow window = null; 37 | // if (WindowIsOpenAlready()) 38 | // { 39 | // if(EditorUtility.DisplayDialog(DialogTitle, DialogMessage, "Yes" , "No, Create New Window")) 40 | // { 41 | // window = EditorWindow.GetWindow(); 42 | // } 43 | // else 44 | // { 45 | // window = ScriptableObject.CreateInstance(); 46 | // window.Show(); 47 | // } 48 | // } 49 | // else 50 | // { 51 | // window = EditorWindow.GetWindow(); 52 | // } 53 | var window = EditorWindow.GetWindow(); 54 | window.titleContent = new GUIContent("Graph Editor"); 55 | return window; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Editor/SleipnirEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a849467b68d24c7ab9eed503ca5fb5c2 3 | timeCreated: 1609817707 -------------------------------------------------------------------------------- /Editor/SleipnirGraphEdgeConnectorListener.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using RedOwl.Sleipnir.Engine; 3 | using UnityEditor.Experimental.GraphView; 4 | using UnityEngine; 5 | using UnityEngine.UIElements; 6 | using PortView = UnityEditor.Experimental.GraphView.Port; 7 | 8 | namespace RedOwl.Sleipnir.Editor 9 | { 10 | /// 11 | /// Custom connector listener so that we can link up nodes and 12 | /// open a search box when the user drops an edge into the canvas 13 | /// 14 | public class SleipnirGraphEdgeConnectorListener : IEdgeConnectorListener 15 | { 16 | private SleipnirGraphViewBase view; 17 | 18 | private GraphViewChange _graphViewChange; 19 | private List _edgesToCreate; 20 | private List _edgesToDelete; 21 | 22 | public SleipnirGraphEdgeConnectorListener(SleipnirGraphViewBase view) 23 | { 24 | this.view = view; 25 | _edgesToCreate = new List(); 26 | _edgesToDelete = new List(); 27 | _graphViewChange.edgesToCreate = _edgesToCreate; 28 | } 29 | 30 | /// 31 | /// Handle connecting nodes when an edge is dropped between two ports 32 | /// 33 | public void OnDrop(GraphView graphView, Edge edge) 34 | { 35 | _edgesToCreate.Clear(); 36 | _edgesToCreate.Add(edge); 37 | _edgesToDelete.Clear(); 38 | if (edge.input.capacity == PortView.Capacity.Single) 39 | { 40 | foreach (Edge connection in edge.input.connections) 41 | { 42 | if (connection != edge) 43 | _edgesToDelete.Add(connection); 44 | } 45 | } 46 | if (edge.output.capacity == PortView.Capacity.Single) 47 | { 48 | foreach (Edge connection in edge.output.connections) 49 | { 50 | if (connection != edge) 51 | _edgesToDelete.Add(connection); 52 | } 53 | } 54 | if (_edgesToDelete.Count > 0) graphView.DeleteElements(_edgesToDelete); 55 | List edgesToCreate = _edgesToCreate; 56 | if (graphView.graphViewChanged != null) edgesToCreate = graphView.graphViewChanged(_graphViewChange).edgesToCreate; 57 | foreach (Edge edge1 in edgesToCreate) 58 | { 59 | graphView.AddElement(edge1); 60 | edge.input.Connect(edge1); 61 | edge.output.Connect(edge1); 62 | } 63 | } 64 | 65 | /// 66 | /// Activate the search dialog when an edge is dropped on an arbitrary location 67 | /// 68 | public void OnDropOutsidePort(Edge edge, Vector2 position) 69 | { 70 | var screenPosition = GUIUtility.GUIToScreenPoint( 71 | Event.current.mousePosition 72 | ); 73 | 74 | if (edge.output != null) 75 | { 76 | view.OpenSearch( 77 | screenPosition, 78 | edge.output.edgeConnector.edgeDragHelper.draggedPort as PortView 79 | ); 80 | } 81 | else if (edge.input != null) 82 | { 83 | view.OpenSearch( 84 | screenPosition, 85 | edge.input.edgeConnector.edgeDragHelper.draggedPort as PortView 86 | ); 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Editor/SleipnirGraphEdgeConnectorListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 259863f935064f8c962a3effddf6c5c7 3 | timeCreated: 1609651864 -------------------------------------------------------------------------------- /Editor/SleipnirGraphSearchProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RedOwl.Sleipnir.Engine; 4 | using UnityEditor.Experimental.GraphView; 5 | using UnityEngine; 6 | 7 | namespace RedOwl.Sleipnir.Editor 8 | { 9 | public struct SearchGroupKey 10 | { 11 | private readonly string _name; 12 | private readonly int _depth; 13 | 14 | public SearchGroupKey(string name, int depth) 15 | { 16 | _name = name; 17 | _depth = depth; 18 | } 19 | 20 | public override int GetHashCode() => _name.GetHashCode() + _depth.GetHashCode(); 21 | } 22 | 23 | public class SearchGroup 24 | { 25 | public SearchTreeGroupEntry Section { get; } 26 | public List Entries { get; } 27 | 28 | public SearchGroup(string name, int depth) 29 | { 30 | 31 | Section = new SearchTreeGroupEntry(new GUIContent(name), depth); 32 | Entries = new List(); 33 | } 34 | 35 | public void Add(NodeAttribute data) 36 | { 37 | Entries.Add(new SearchTreeEntry(new GUIContent(data.Name)){ userData = data, level = Section.level + 1}); 38 | } 39 | } 40 | 41 | public class SleipnirGraphSearchProvider : ScriptableObject, ISearchWindowProvider 42 | { 43 | private SleipnirGraphViewBase _view; 44 | 45 | private GraphAttribute _graphTypeData; 46 | private bool _useGraphTagMatching; 47 | 48 | public void Initialize(SleipnirGraphViewBase view) 49 | { 50 | _view = view; 51 | bool found = GraphAttribute.Cache.TryGet(_view.Graph.GetType(), out _graphTypeData); 52 | _useGraphTagMatching = found && _graphTypeData?.Tags.Count > 0; 53 | } 54 | 55 | public List CreateSearchTree(SearchWindowContext context) 56 | { 57 | var tree = new List 58 | { 59 | new SearchTreeGroupEntry(new GUIContent("Create Node"), 0), 60 | }; 61 | foreach (var group in GetSearchGroups()) 62 | { 63 | tree.Add(group.Section); 64 | foreach (var entry in group.Entries) 65 | { 66 | tree.Add(entry); 67 | } 68 | } 69 | return tree; 70 | } 71 | 72 | public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context) 73 | { 74 | _view.CreateNode((NodeAttribute)entry.userData, context.screenMousePosition); 75 | return true; 76 | } 77 | 78 | private IEnumerable GetSearchGroups() 79 | { 80 | Dictionary groups = new Dictionary(); 81 | foreach (var node in NodeAttribute.Cache.All) 82 | { 83 | if (_useGraphTagMatching && !_graphTypeData.Tags.Overlaps(node.Tags)) continue; 84 | SearchGroup searchGroup = null; 85 | int depth = 1; 86 | 87 | foreach (string subsection in node.Path.Split('/')) 88 | { 89 | var key = new SearchGroupKey(subsection, depth); 90 | if (!groups.TryGetValue(key, out searchGroup)) 91 | { 92 | searchGroup = new SearchGroup(subsection, depth); 93 | groups.Add(key, searchGroup); 94 | } 95 | depth++; 96 | } 97 | 98 | searchGroup?.Add(node); 99 | } 100 | 101 | var data = new List(groups.Values); 102 | data.Sort((a, b) => string.Compare(a.Section.name, b.Section.name, StringComparison.Ordinal)); 103 | foreach (var group in data) 104 | { 105 | yield return group; 106 | } 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /Editor/SleipnirGraphSearchProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f49353be53f941cd874e7dee1614cd20 3 | timeCreated: 1607196817 -------------------------------------------------------------------------------- /Editor/SleipnirGraphViewBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RedOwl.Sleipnir.Engine; 4 | using UnityEditor; 5 | using UnityEditor.Experimental.GraphView; 6 | using UnityEngine; 7 | using UnityEngine.UIElements; 8 | using PortView = UnityEditor.Experimental.GraphView.Port; 9 | 10 | namespace RedOwl.Sleipnir.Editor 11 | { 12 | public abstract class SleipnirGraphViewBase : GraphView 13 | { 14 | public IGraphAsset GraphAsset { get; protected set; } 15 | public IGraph Graph => GraphAsset.Graph; 16 | public GridBackground GridBackground { get; private set; } 17 | public SleipnirGraphSearchProvider SearchProvider { get; private set; } 18 | public MiniMap MiniMap { get; private set; } 19 | public IEdgeConnectorListener EdgeConnectorListener { get; } 20 | 21 | protected Dictionary _nodeViewCache; 22 | 23 | protected SleipnirGraphViewBase() 24 | { 25 | EdgeConnectorListener = new SleipnirGraphEdgeConnectorListener(this); 26 | } 27 | 28 | protected void CreateGridBackground() 29 | { 30 | GridBackground = new GridBackground {name = "Grid"}; 31 | Insert(0, GridBackground); 32 | } 33 | 34 | protected void CreateMiniMap() 35 | { 36 | MiniMap = new MiniMap {anchored = true, maxWidth = 200, maxHeight = 100, visible = false}; 37 | Add(MiniMap); 38 | } 39 | 40 | protected void CreateSearch() 41 | { 42 | SearchProvider = ScriptableObject.CreateInstance(); 43 | SearchProvider.Initialize(this); 44 | nodeCreationRequest = ctx => SearchWindow.Open(new SearchWindowContext(ctx.screenMousePosition), SearchProvider); 45 | } 46 | 47 | public void CreateNode(NodeAttribute data, Vector2 position) 48 | { 49 | RecordUndo("Create Node"); 50 | var node = (INode)Activator.CreateInstance(data.Type); 51 | Graph.Add(node); // Definition & Initialize are called here 52 | var window = EditorWindow.GetWindow(); 53 | node.NodePosition = window.rootVisualElement.ChangeCoordinatesTo(contentViewContainer, position - window.position.position - new Vector2(3, 26)); 54 | CreateNodeView(node); 55 | Save(); 56 | } 57 | 58 | protected void RecordUndo(string title) 59 | { 60 | if (GraphAsset != null) Undo.RecordObject((ScriptableObject)GraphAsset, title); 61 | } 62 | 63 | public void OpenSearch(Vector2 screenPosition, PortView port = null) 64 | { 65 | //_search.SourcePort = connectedPort; 66 | SearchWindow.Open(new SearchWindowContext(screenPosition), SearchProvider); 67 | } 68 | 69 | #region API 70 | 71 | protected abstract void CreateNodeView(INode node); 72 | public abstract void Save(); 73 | 74 | #endregion 75 | } 76 | 77 | public abstract class SleipnirGraphViewBase : SleipnirGraphViewBase where TNodeView : SleipnirNodeViewBase, new() 78 | { 79 | protected override void CreateNodeView(INode node) 80 | { 81 | var info = NodeAttribute.Cache[node.GetType()]; 82 | GraphElement element = info.EditorView != null ? (GraphElement)Activator.CreateInstance(info.EditorView) : new TNodeView(); 83 | if (element is IEditorNodeView editorView) editorView.EdgeListener = EdgeConnectorListener; 84 | if (element is INodeView nodeView) 85 | { 86 | nodeView.Initialize(node, info); 87 | _nodeViewCache.Add(node.NodeId, nodeView); 88 | } 89 | AddElement(element); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Editor/SleipnirGraphViewBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b96aa4253d5d45f59139f1252d53e441 3 | timeCreated: 1609796068 -------------------------------------------------------------------------------- /Editor/SleipnirNodeViewBase.cs: -------------------------------------------------------------------------------- 1 | using RedOwl.Sleipnir.Engine; 2 | using UnityEditor.Experimental.GraphView; 3 | using UnityEngine; 4 | using UnityEngine.UIElements; 5 | using NodeView = UnityEditor.Experimental.GraphView.Node; 6 | using PortView = UnityEditor.Experimental.GraphView.Port; 7 | 8 | namespace RedOwl.Sleipnir.Editor 9 | { 10 | public interface IEditorNodeView : INodeView 11 | { 12 | IEdgeConnectorListener EdgeListener { get; set; } 13 | } 14 | 15 | public class SleipnirNodeViewBase : NodeView, IEditorNodeView 16 | { 17 | public IEdgeConnectorListener EdgeListener { get; set; } 18 | public VisualElement ValueInPortContainer => inputContainer; 19 | public VisualElement ValueOutPortContainer => outputContainer; 20 | public VisualElement FlowInPortContainer { get; private set; } 21 | public VisualElement FlowOutPortContainer { get; private set; } 22 | public INode Node => (INode) userData; 23 | 24 | public NodeAttribute ReflectionData { get; private set; } 25 | 26 | public bool IsMoveable => ReflectionData.Moveable; 27 | 28 | #region API 29 | protected virtual void OnInitialize() { } 30 | protected virtual void OnDestroy() { } 31 | protected virtual void OnError() { } 32 | #endregion 33 | 34 | public void Initialize(INode node, NodeAttribute data) 35 | { 36 | userData = node; 37 | ReflectionData = data; 38 | name = node.NodeId; 39 | style.position = Position.Absolute; 40 | style.left = node.NodePosition.x; 41 | style.top = node.NodePosition.y; 42 | style.minWidth = data.MinSize.x; 43 | style.minHeight = data.MinSize.y; 44 | title = $"{ReflectionData.Name}"; 45 | tooltip = ReflectionData.Tooltip; 46 | if (!ReflectionData.Deletable) 47 | { 48 | capabilities &= ~Capabilities.Deletable; 49 | } 50 | if (!ReflectionData.Moveable) 51 | { 52 | capabilities &= ~Capabilities.Movable; 53 | } 54 | 55 | CreateBody(node); 56 | CreateFlowPortContainers(); 57 | CreateExecuteButton(node); 58 | CreateFlowPorts(node); 59 | AttachFlowPortContainers(); 60 | CreateValuePorts(node); 61 | RefreshExpandedState(); 62 | RefreshPorts(); 63 | 64 | RegisterCallback((e) => Destroy()); 65 | 66 | OnInitialize(); 67 | } 68 | 69 | internal void Destroy() 70 | { 71 | OnDestroy(); 72 | } 73 | 74 | private void CreateBody(INode node) 75 | { 76 | #if ODIN_INSPECTOR 77 | var tree = Sirenix.OdinInspector.Editor.PropertyTree.Create(node); 78 | bool useUndo = node is Object; 79 | extensionContainer.Add(new IMGUIContainer(() => 80 | { 81 | Sirenix.Utilities.Editor.GUIHelper.PushLabelWidth(100); 82 | tree.Draw(useUndo); 83 | Sirenix.Utilities.Editor.GUIHelper.PopLabelWidth(); 84 | }) { name = "OdinTree"}); 85 | #else 86 | // TODO: Draw Property Field's with UI Elements 87 | // http://wiki.unity3d.com/index.php/ExposePropertiesInInspector_Generic 88 | #endif 89 | } 90 | 91 | private PortView CreatePortView(IPort port, Orientation orientation) 92 | { 93 | var view = new SleipnirPortView(orientation, port.Direction, port.Capacity, port.ValueType, EdgeListener) 94 | { 95 | name = port.Name, 96 | userData = port, 97 | portName = port.Name, 98 | }; 99 | return view; 100 | } 101 | 102 | private void CreateValuePorts(INode node) 103 | { 104 | foreach (var valuePort in node.ValueInPorts.Values) 105 | { 106 | ValueInPortContainer.Add(CreatePortView(valuePort, Orientation.Horizontal)); 107 | } 108 | 109 | foreach (var valuePort in node.ValueOutPorts.Values) 110 | { 111 | ValueOutPortContainer.Add(CreatePortView(valuePort, Orientation.Horizontal)); 112 | } 113 | } 114 | 115 | private void CreateFlowPortContainers() 116 | { 117 | FlowInPortContainer = new VisualElement {name = "FlowPorts"}; 118 | FlowInPortContainer.AddToClassList("FlowInPorts"); 119 | FlowOutPortContainer = new VisualElement {name = "FlowPorts"}; 120 | FlowOutPortContainer.AddToClassList("FlowOutPorts"); 121 | } 122 | 123 | private void CreateExecuteButton(INode node) 124 | { 125 | if (!node.IsFlowRoot) return; 126 | var button = new Button(() => new Flow(Node.Graph, node).Execute()) {text = "Execute"}; 127 | titleButtonContainer.Add(button); 128 | } 129 | 130 | private void CreateFlowPorts(INode node) 131 | { 132 | foreach (var flowPort in node.FlowInPorts.Values) 133 | { 134 | FlowInPortContainer.Add(CreatePortView(flowPort, Orientation.Vertical)); 135 | } 136 | 137 | foreach (var flowPort in node.FlowOutPorts.Values) 138 | { 139 | FlowOutPortContainer.Add(CreatePortView(flowPort, Orientation.Vertical)); 140 | } 141 | } 142 | 143 | private void AttachFlowPortContainers() 144 | { 145 | if (FlowInPortContainer.childCount > 0) mainContainer.parent.Insert(0, FlowInPortContainer); 146 | if (FlowOutPortContainer.childCount > 0) mainContainer.parent.Add(FlowOutPortContainer); 147 | } 148 | } 149 | 150 | public static class NodeViewExtensions 151 | { 152 | //public static INode INode(this NodeView self) => (INode) self.userData; 153 | // public static string Id(this NodeView self) => self.INode().NodeId; 154 | // public static uint Id(this PortView self) => (uint)self.userData; 155 | } 156 | } -------------------------------------------------------------------------------- /Editor/SleipnirNodeViewBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: faffa3b7c2ba4f038a94af0ecf8a6e7b 3 | timeCreated: 1606410864 -------------------------------------------------------------------------------- /Editor/SleipnirPortView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RedOwl.Sleipnir.Engine; 3 | using UnityEditor.Experimental.GraphView; 4 | using UnityEngine.UIElements; 5 | using PortView = UnityEditor.Experimental.GraphView.Port; 6 | 7 | namespace RedOwl.Sleipnir.Editor 8 | { 9 | public class SleipnirPortView : PortView 10 | { 11 | public SleipnirPortView(Orientation orientation, PortDirection direction, PortCapacity capacity, Type type, IEdgeConnectorListener listener) : base(orientation, ConvertDirection(direction), ConvertCapacity(capacity), type) 12 | { 13 | m_EdgeConnector = new EdgeConnector(listener); 14 | this.AddManipulator(m_EdgeConnector); 15 | } 16 | 17 | private static Direction ConvertDirection(PortDirection value) => value == PortDirection.Input ? Direction.Input : Direction.Output; 18 | private static Capacity ConvertCapacity(PortCapacity value) => value == PortCapacity.Single ? Capacity.Single : Capacity.Multi; 19 | } 20 | } -------------------------------------------------------------------------------- /Editor/SleipnirPortView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa6f5d9f606d4e138f47927439c77658 3 | timeCreated: 1609653001 -------------------------------------------------------------------------------- /Editor/SleipnirWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using RedOwl.Sleipnir.Engine; 3 | using UnityEditor; 4 | using UnityEditor.UIElements; 5 | using UnityEngine; 6 | using UnityEngine.UIElements; 7 | 8 | namespace RedOwl.Sleipnir.Editor 9 | { 10 | // TODO: what happens when we delete the GraphReference this is current viewing?! 11 | public class SleipnirWindow : EditorWindow 12 | { 13 | private Stack _graphs; 14 | private IGraphAsset _current; 15 | private IGraphView _view; 16 | 17 | private Toolbar _toolbar; 18 | private ToolbarBreadcrumbs _toolbarBreadcrumbs; 19 | 20 | public void OnEnable() 21 | { 22 | rootVisualElement.styleSheets.Add(Resources.Load("SleipnirWindow")); 23 | if (EditorApplication.isPlayingOrWillChangePlaymode) return; 24 | _graphs = new Stack(5); 25 | CreateView(); 26 | CreateToolbar(); 27 | Undo.undoRedoPerformed += Reload; 28 | Reload(); 29 | } 30 | 31 | public void OnDisable() 32 | { 33 | Undo.undoRedoPerformed -= Reload; 34 | } 35 | 36 | private void Reload() 37 | { 38 | _view.Reload(); 39 | } 40 | 41 | private void HandleBreadcrumbClick(int index) 42 | { 43 | _view.Save(); 44 | while (_graphs.Count > index) 45 | { 46 | _graphs.Pop(); 47 | _toolbarBreadcrumbs.PopItem(); 48 | } 49 | 50 | _current = _graphs.Peek(); 51 | LoadCurrent(); 52 | } 53 | 54 | internal void Load(IGraphAsset asset, bool nested = false) 55 | { 56 | if (asset == null) return; 57 | _view.Save(); 58 | 59 | if (!nested) 60 | { 61 | _graphs.Clear(); 62 | while (_toolbarBreadcrumbs.childCount > 0) _toolbarBreadcrumbs.PopItem(); 63 | } 64 | _current = asset; 65 | _graphs.Push(asset); 66 | int lastIndex = _graphs.Count; 67 | _toolbarBreadcrumbs.PushItem(_current.name, () => { HandleBreadcrumbClick(lastIndex); }); 68 | 69 | LoadCurrent(); 70 | } 71 | 72 | internal void LoadCurrent() 73 | { 74 | _view.Load(_current); 75 | } 76 | 77 | private void CreateView() 78 | { 79 | _view = EditorApplication.isPlayingOrWillChangePlaymode ? (IGraphView) new SleipnirGraphViewPlaymode() : new SleipnirGraphViewEditmode(); 80 | if (_view is VisualElement element) 81 | { 82 | rootVisualElement.Add(element); 83 | element.StretchToParentSize(); 84 | } 85 | } 86 | 87 | private void CreateToolbar() 88 | { 89 | _toolbar = new Toolbar(); 90 | 91 | _toolbarBreadcrumbs = new ToolbarBreadcrumbs(); 92 | _toolbar.Add(_toolbarBreadcrumbs); 93 | 94 | var spacer = new ToolbarSpacer {flex = true}; 95 | _toolbar.Add(spacer); 96 | 97 | var saveBtn = new Button(_view.Save) {text = "Save"}; 98 | _toolbar.Add(saveBtn); 99 | 100 | var executeBtn = new Button(Execute) {text = "Execute All"}; 101 | _toolbar.Add(executeBtn); 102 | 103 | var search = new ToolbarSearchField(); 104 | _toolbar.Add(search); 105 | 106 | var clearBtn = new Button(Clear) {text = "Clear"}; 107 | _toolbar.Add(clearBtn); 108 | 109 | rootVisualElement.Add(_toolbar); 110 | } 111 | 112 | private void Execute() 113 | { 114 | if (_current != null && _current.Graph != null) new Flow(_current.Graph).Execute(); 115 | } 116 | 117 | private const string ClearTitle = "Clear Graph?"; 118 | private const string ClearMessage = "Clear all nodes and connections from this graph?"; 119 | 120 | private void Clear() 121 | { 122 | if (EditorUtility.DisplayDialog(ClearTitle, ClearMessage, "Yes") && _current != null) 123 | { 124 | _current.Graph?.Clear(); 125 | Reload(); 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /Editor/SleipnirWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b38d66f72f34457b557cc4c904efa84 3 | timeCreated: 1606517062 -------------------------------------------------------------------------------- /Engine.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ef5fc69b5c4afe498436d6f4164ece3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Engine/Flow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | public interface IFlow 9 | { 10 | IGraph Graph { get; } 11 | IEnumerable RootNodes { get; } 12 | bool ContainsKey(PortId id); 13 | bool ContainsKey(string key); 14 | T Get(PortId id); 15 | T Get(string key); 16 | void Set(PortId id, T value); 17 | void Set(string key, T value); 18 | void Clear(); 19 | void Execute(); 20 | } 21 | 22 | public class Flow : BetterDictionary, IFlow 23 | { 24 | public IGraph Graph { get; } 25 | 26 | private readonly INode[] rootNodes; 27 | public IEnumerable RootNodes => rootNodes; 28 | 29 | public Flow(IGraph graph) 30 | { 31 | Graph = graph; 32 | rootNodes = graph.GetRootNodes() as INode[]; 33 | } 34 | 35 | public Flow(IGraph graph, params INode[] nodes) 36 | { 37 | Graph = graph; 38 | rootNodes = nodes; 39 | } 40 | 41 | private string FormatKey(PortId id) => $"{id.Node}.{id.Port}"; 42 | 43 | public bool ContainsKey(PortId id) => ContainsKey(FormatKey(id)); 44 | 45 | public T Get(PortId id) => Get(FormatKey(id)); 46 | public T Get(string key) 47 | { 48 | var type = typeof(T); 49 | var value = this[key]; 50 | switch (value) 51 | { 52 | case null when type.IsValueType: 53 | throw new InvalidCastException($"Cannot cast null to value type `{type.FullName}`"); 54 | case null: 55 | case T _: // Short circuit Convert.ChangeType if we can cast quicker 56 | return (T)value; 57 | default: // Try for IConvertible support 58 | try 59 | { 60 | return (T)Convert.ChangeType(value, type); 61 | } 62 | catch (Exception e) 63 | { 64 | throw new InvalidCastException($"Cannot cast `{value.GetType()}` to `{type}`. Error: {e}."); 65 | } 66 | } 67 | } 68 | 69 | public void Set(PortId id, T value) => Set(FormatKey(id), value); 70 | 71 | public void Set(string key, T value) 72 | { 73 | if (ContainsKey(key)) 74 | { 75 | this[key] = value; 76 | } 77 | else 78 | { 79 | Add(key, value); 80 | } 81 | } 82 | 83 | public void Execute() => Walk(this); 84 | 85 | #region FlowAPI 86 | 87 | private void Walk(IFlow flow) 88 | { 89 | // TODO: there appears to be an initialization bug with ValueNodes and their default value not getting setup 90 | flow.Graph.Initialize(ref flow); 91 | foreach (var rootNode in flow.RootNodes) 92 | { 93 | WalkFlowPorts(rootNode); 94 | } 95 | } 96 | 97 | private void WalkFlowPorts(INode node) 98 | { 99 | WalkValuePorts(node); 100 | foreach (var port in node.FlowOutPorts.Values) 101 | { 102 | WalkFlowPort(port); 103 | } 104 | } 105 | 106 | private void WalkFlowPort(IFlowPort port) 107 | { 108 | foreach (var next in port.Execute()) 109 | { 110 | // TODO: Handle Yield Instructions / Custom Yield Instructions 111 | if (next is IFlowPort nextPort) 112 | { 113 | // Debug.Log($"Moving Towards Next Port {nextPort}"); 114 | if (nextPort.Direction == PortDirection.Input) 115 | { 116 | WalkValuePorts(nextPort.Node); 117 | } 118 | WalkFlowPort(nextPort); 119 | } 120 | } 121 | } 122 | 123 | private void WalkValuePorts(INode node) 124 | { 125 | // Debug.Log($"Walking Node '{node}' for Values"); 126 | foreach (var port in node.ValueInPorts.Values) 127 | { 128 | WalkValuePort(port); 129 | } 130 | } 131 | 132 | private void WalkValuePort(IValuePort port) 133 | { 134 | // Debug.Log($"Walking Value Port {port}"); 135 | foreach (var next in port.Execute()) 136 | { 137 | // TODO: Handle Yield Instructions / Custom Yield Instructions 138 | if (next is IValuePort nextPort) 139 | { 140 | // Debug.Log($"Moving Towards Next Port {nextPort}"); 141 | if (nextPort.Direction == PortDirection.Output) 142 | { 143 | WalkValuePort(nextPort); 144 | } 145 | } 146 | } 147 | } 148 | 149 | #endregion 150 | } 151 | 152 | public class Flow : Flow where T : INode 153 | { 154 | public Flow(IGraph graph) : base(graph, graph.GetNodes(typeof(T)).ToArray()) {} 155 | } 156 | 157 | // TODO: AsyncFlow ? 158 | } 159 | -------------------------------------------------------------------------------- /Engine/Flow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0649244db4230154dad401aa48a8a245 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Graph.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 32bbb108dae2437fb15fa888216d10e7 3 | timeCreated: 1609650391 -------------------------------------------------------------------------------- /Engine/Graph/Connection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | [Serializable] 8 | public class PortCollection : BetterCollection {} 9 | 10 | [Serializable] 11 | public class ConnectionsGraph : BetterDictionary 12 | { 13 | public PortCollection SafeGet(PortId key) 14 | { 15 | try 16 | { 17 | return this[key]; 18 | } 19 | catch (KeyNotFoundException) 20 | { 21 | return new PortCollection(); 22 | } 23 | } 24 | 25 | public void Connect(PortId key, PortId port) 26 | { 27 | if (TryGetValue(key, out var collection)) 28 | { 29 | collection.Add(port); 30 | return; 31 | } 32 | 33 | collection = new PortCollection{port}; 34 | Add(key, collection); 35 | } 36 | 37 | public void Disconnect(PortId key, PortId port) 38 | { 39 | if (TryGetValue(key, out var collection)) 40 | { 41 | collection.Remove(port); 42 | this[key] = collection; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Engine/Graph/Connection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 58de9d2a8fb053b469d5c2fe0089a6fd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Graph/Graph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JetBrains.Annotations; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | public interface IGraph : INode 9 | { 10 | IEnumerable Nodes { get; } 11 | int NodeCount { get; } 12 | INode GetNode(string id); 13 | IEnumerable GetNodes(Type type); 14 | T Add(T node) where T : INode; 15 | bool Get(string id, out INode node); 16 | void Remove(T node) where T : INode; 17 | void Clear(); 18 | 19 | ConnectionsGraph ValueInConnections { get; } 20 | ConnectionsGraph FlowOutConnections { get; } 21 | void Connect(IPort output, IPort input); 22 | void Disconnect(IPort output, IPort input); 23 | 24 | void NotifyDirty(); 25 | } 26 | 27 | [Serializable] 28 | [Graph] 29 | [Node("Common", Name = "SubGraph", Path = "Common")] 30 | public class Graph : Node, IGraph 31 | { 32 | [SerializeReference] 33 | [HideInInspector] 34 | private List _nodes; 35 | 36 | public IEnumerable Nodes => _nodes; 37 | public int NodeCount => _nodes.Count; 38 | 39 | [SerializeField] 40 | [HideInInspector] 41 | private ConnectionsGraph valueInConnections; 42 | public ConnectionsGraph ValueInConnections => valueInConnections; 43 | 44 | [SerializeField] 45 | [HideInInspector] 46 | private ConnectionsGraph flowOutConnections; 47 | public ConnectionsGraph FlowOutConnections => flowOutConnections; 48 | 49 | public Graph() 50 | { 51 | Clear(); 52 | } 53 | 54 | private void EnsureRequiredNodes(GraphAttribute data) 55 | { 56 | foreach (var attribute in data.RequiredNodes) 57 | { 58 | var nodes = new List(GetNodes(attribute.Type)); 59 | if (nodes.Count == 0) 60 | { 61 | var node = (INode)Activator.CreateInstance(attribute.Type); 62 | // TODO: Change capabilities of required node to not be moveable or deleteable 63 | Add(node); // Definition & Initialize are called here 64 | node.NodePosition = attribute.Position; 65 | } 66 | } 67 | } 68 | 69 | protected override void OnDirty() 70 | { 71 | foreach (var node in _nodes) 72 | { 73 | node.MarkDirty(); 74 | } 75 | } 76 | 77 | public void NotifyDirty() 78 | { 79 | OnGraphChanged(); 80 | } 81 | 82 | protected virtual void OnGraphChanged() {} 83 | 84 | protected override void OnDefinition() 85 | { 86 | if (GraphAttribute.Cache.TryGet(GetType(), out var data)) 87 | { 88 | EnsureRequiredNodes(data); 89 | } 90 | 91 | foreach (var node in _nodes) 92 | { 93 | node.Definition(this); 94 | } 95 | } 96 | 97 | protected override void OnInitialize(ref IFlow flow) 98 | { 99 | foreach (var node in _nodes) 100 | { 101 | node.Initialize(ref flow); 102 | } 103 | } 104 | 105 | [CanBeNull] 106 | public INode GetNode(string id) 107 | { 108 | foreach (var node in _nodes) 109 | { 110 | if (node.NodeId == id) return node; 111 | } 112 | Debug.Log($"Cannot Find Node '{id}' in graph"); 113 | return null; 114 | } 115 | 116 | public IEnumerable GetNodes(Type nodeType) 117 | { 118 | foreach (var node in _nodes) 119 | { 120 | if (nodeType.IsInstanceOfType(node)) 121 | yield return node; 122 | } 123 | } 124 | 125 | public T Add(T node) where T : INode 126 | { 127 | _nodes.Add(node); 128 | node.Definition(this); 129 | return node; 130 | } 131 | 132 | public bool Get(string id, out INode node) 133 | { 134 | foreach (var n in _nodes) 135 | { 136 | if (n.NodeId != id) continue; 137 | node = n; 138 | return true; 139 | } 140 | 141 | node = null; 142 | return false; 143 | } 144 | 145 | public void Remove(T node) where T : INode 146 | { 147 | // if (node is IGraphPort) IsDefined = false; 148 | // TODO: If _nodes was a dictionary this would be easier 149 | for (int i = _nodes.Count - 1; i >= 0; i--) 150 | { 151 | var n = _nodes[i]; 152 | if (n.NodeId == node.NodeId) 153 | { 154 | CleanupFlowPortConnections(n); 155 | CleanupValuePortConnections(n); 156 | _nodes.RemoveAt(i); 157 | } 158 | } 159 | } 160 | 161 | public void Clear() 162 | { 163 | _nodes = new List(); 164 | valueInConnections = new ConnectionsGraph(); 165 | flowOutConnections = new ConnectionsGraph(); 166 | // IsDefined = false; 167 | } 168 | 169 | private void CleanupFlowPortConnections(INode target) 170 | { 171 | foreach (var port in target.FlowOutPorts.Values) 172 | { 173 | FlowOutConnections.Remove(port.Id); 174 | } 175 | } 176 | 177 | private void CleanupValuePortConnections(INode target) 178 | { 179 | foreach (var port in target.ValueInPorts.Values) 180 | { 181 | ValueInConnections.Remove(port.Id); 182 | } 183 | } 184 | 185 | // Value Ports - Input -> [Output, Output] 186 | // Flow Port - Output -> [Input, Input] 187 | public void Connect(IPort output, IPort input) 188 | { 189 | // TODO: if we can figure out what node each port came from we can stop storing the node ID with the port 190 | if (input is IValuePort valueIn && output is IValuePort valueOut) 191 | valueInConnections.Connect(valueIn.Id, valueOut.Id); 192 | if (input is IFlowPort flowIn && output is IFlowPort flowOut) 193 | flowOutConnections.Connect(flowOut.Id, flowIn.Id); 194 | } 195 | 196 | public void Disconnect(IPort output, IPort input) 197 | { 198 | if (input is IValuePort valueIn && output is IValuePort valueOut) 199 | valueInConnections.Disconnect(valueIn.Id, valueOut.Id); 200 | if (input is IFlowPort flowIn && output is IFlowPort flowOut) 201 | flowOutConnections.Disconnect(flowOut.Id, flowIn.Id); 202 | } 203 | 204 | public override string ToString() 205 | { 206 | return $"{GetType().Name}({NodeId.Substring(0,8)})"; 207 | } 208 | } 209 | 210 | public static class GraphExtensions 211 | { 212 | public static IEnumerable GetNodes(this IGraph graph) where T : INode 213 | { 214 | foreach(var node in graph.GetNodes(typeof(T))) 215 | { 216 | yield return (T) node; 217 | } 218 | } 219 | 220 | public static IEnumerable GetRootNodes(this IGraph graph) 221 | { 222 | foreach (var node in graph.Nodes) 223 | { 224 | if (node.IsFlowRoot) yield return node; 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Engine/Graph/Graph.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e0e5ff30836c03c449e68b8120a7e030 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Graph/GraphAsset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using Sirenix.OdinInspector; 5 | using UnityEngine; 6 | using Debug = UnityEngine.Debug; 7 | 8 | namespace RedOwl.Sleipnir.Engine 9 | { 10 | public interface IGraphAsset 11 | { 12 | string name { get; } 13 | IGraph Graph { get; set; } 14 | } 15 | 16 | public abstract class GraphAsset : GraphAssetObject, IGraphAsset where T : IGraph, new() 17 | { 18 | [SerializeReference, InlineEditor()] 19 | private IGraph _graph = new T(); 20 | 21 | public IGraph Graph 22 | { 23 | get => _graph; 24 | set => _graph = value; 25 | } 26 | } 27 | 28 | [CreateAssetMenu(menuName = "Red Owl/Graph", fileName = "Graph")] 29 | public class GraphAsset : GraphAsset {} 30 | } -------------------------------------------------------------------------------- /Engine/Graph/GraphAsset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f29b0a106f61ac4b914e77aa5fee0a1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Graph/GraphAssetObject.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public abstract class GraphAssetObject : ScriptableObject {} 6 | } -------------------------------------------------------------------------------- /Engine/Graph/GraphAssetObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c9aea017bd94a5ab4111e16a8f8e131 3 | timeCreated: 1610688792 -------------------------------------------------------------------------------- /Engine/Graph/GraphAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEngine.Scripting; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] 9 | public sealed class GraphAttribute : PreserveAttribute 10 | { 11 | public static readonly TypeCache Cache = new TypeCache((Type graphType, ref GraphAttribute attribute, out Type key) => 12 | { 13 | attribute.Initialize(graphType); 14 | key = graphType; 15 | return true; 16 | }); 17 | 18 | 19 | public HashSet Tags { get; } 20 | 21 | public RequireNodeAttribute[] RequiredNodes { get; private set; } 22 | 23 | public GraphAttribute(params string[] tags) 24 | { 25 | Tags = new HashSet(tags); 26 | } 27 | 28 | private void Initialize(Type type) 29 | { 30 | RequiredNodes = type.GetCustomAttributes() as RequireNodeAttribute[]; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Engine/Graph/GraphAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ac85c944bce4c42b0f3af2a07e3dc79 3 | timeCreated: 1606796689 -------------------------------------------------------------------------------- /Engine/Graph/GraphController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | #if ODIN_INSPECTOR 7 | [Sirenix.OdinInspector.HideMonoScript] 8 | #endif 9 | public class GraphController : MonoBehaviour 10 | { 11 | [Flags] 12 | public enum Stages 13 | { 14 | None = 0b_00000, 15 | Awake = 0b_00001, 16 | Start = 0b_00010, 17 | Update = 0b_00100, 18 | FixedUpdate = 0b_01000, 19 | LateUpdate = 0b_10000, 20 | All = 0b_11111 21 | } 22 | 23 | public GraphAsset asset; 24 | public Stages stage; 25 | 26 | private IFlow _awakeFlow; 27 | private IFlow _startFlow; 28 | private IFlow _updateFlow; 29 | private IFlow _fixedUpdateFlow; 30 | private IFlow _lateUpdateFlow; 31 | 32 | private void Awake() 33 | { 34 | _awakeFlow = new Flow(asset.Graph); 35 | _startFlow = new Flow(asset.Graph); 36 | _updateFlow = new Flow(asset.Graph); 37 | _fixedUpdateFlow = new Flow(asset.Graph); 38 | _lateUpdateFlow = new Flow(asset.Graph); 39 | 40 | if (stage.HasFlag(Stages.Awake)) _awakeFlow.Execute(); 41 | } 42 | 43 | private void Start() 44 | { 45 | if (stage.HasFlag(Stages.Start)) _startFlow.Execute(); 46 | } 47 | 48 | private void Update() 49 | { 50 | if (stage.HasFlag(Stages.Update)) _updateFlow.Execute(); 51 | } 52 | 53 | private void FixedUpdate() 54 | { 55 | if (stage.HasFlag(Stages.FixedUpdate)) _fixedUpdateFlow.Execute(); 56 | } 57 | 58 | private void LateUpdate() 59 | { 60 | if (stage.HasFlag(Stages.LateUpdate)) _lateUpdateFlow.Execute(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Engine/Graph/GraphController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 664a79a959043c946bfabf987f07e3e4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Graph/GraphView.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public interface IGraphView 4 | { 5 | void Load(IGraphAsset asset); 6 | void Reload(); 7 | void Save(); 8 | } 9 | 10 | public abstract class RuntimeGraphView : IGraphView 11 | { 12 | public void Load(IGraphAsset asset) {} 13 | 14 | public void Reload() {} 15 | 16 | public void Save() {} 17 | } 18 | } -------------------------------------------------------------------------------- /Engine/Graph/GraphView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cfd7a62edadd412385dd91d14161b1b4 3 | timeCreated: 1609795368 -------------------------------------------------------------------------------- /Engine/Graph/ShowInNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sirenix.OdinInspector; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] 7 | #if ODIN_INSPECTOR 8 | public class ShowInNode : ShowInInspectorAttribute 9 | #else 10 | public class ShowInNode : Attribute 11 | #endif 12 | { 13 | /// 14 | /// Display name of the editable field. 15 | /// 16 | /// If not supplied, this will be inferred based on the field name. 17 | /// 18 | public string Name { get; set; } 19 | 20 | public ShowInNode(string name = null) 21 | { 22 | Name = name; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Engine/Graph/ShowInNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87d124c125d540328a84e0412fff1299 3 | timeCreated: 1609933987 -------------------------------------------------------------------------------- /Engine/Graph/Types.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b039ff9a6e184b2b8ce0c9c65c6030ad 3 | timeCreated: 1610109326 -------------------------------------------------------------------------------- /Engine/Graph/Types/FlowGraph.cs: -------------------------------------------------------------------------------- 1 | // namespace RedOwl.Sleipnir.Engine 2 | // { 3 | // [Graph("Common", "Flow")] 4 | // [RequireNode(typeof(StartNode), 200, 100)] 5 | // [RequireNode(typeof(UpdateNode), 400, 100)] 6 | // public class FlowGraph : Graph 7 | // { 8 | // 9 | // } 10 | // } -------------------------------------------------------------------------------- /Engine/Graph/Types/FlowGraph.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eb39e76b46ec458981be97aa574bfb88 3 | timeCreated: 1609650401 -------------------------------------------------------------------------------- /Engine/Helpers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4304599ce2704ef2b920105298bd92b2 3 | timeCreated: 1609625844 -------------------------------------------------------------------------------- /Engine/Helpers/BetterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | [Serializable] 9 | public abstract class BetterCollection : ICollection 10 | { 11 | [SerializeField] 12 | private List _collection; 13 | 14 | protected BetterCollection() 15 | { 16 | _collection = new List(); 17 | } 18 | 19 | protected BetterCollection(int capacity) 20 | { 21 | _collection = new List(capacity); 22 | } 23 | 24 | public int Count => _collection.Count; 25 | 26 | public bool IsReadOnly => false; 27 | 28 | public void Clear() => _collection.Clear(); 29 | 30 | public bool Contains(T item) => _collection.Contains(item); 31 | 32 | public void CopyTo(T[] array, int index) => _collection.CopyTo(array, index); 33 | 34 | public void Add(T item) => _collection.Add(item); 35 | 36 | public void Remove(T value) 37 | { 38 | for (int i = _collection.Count - 1; i >= 0; i--) 39 | { 40 | if (_collection[i].Equals(value)) 41 | { 42 | _collection.RemoveAt(i); 43 | } 44 | } 45 | } 46 | 47 | public void Remove(int index) => _collection.RemoveAt(index); 48 | 49 | bool ICollection.Remove(T item) => _collection.Remove(item); 50 | 51 | public IEnumerator GetEnumerator() => _collection.GetEnumerator(); 52 | 53 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 54 | } 55 | } -------------------------------------------------------------------------------- /Engine/Helpers/BetterCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 16bf64d1c6584aaa9c5662c76b6a801c 3 | timeCreated: 1607655494 -------------------------------------------------------------------------------- /Engine/Helpers/BetterDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | [Serializable] 8 | public class BetterDictionary : Dictionary, ISerializationCallbackReceiver 9 | { 10 | [SerializeField] 11 | private List _keys; 12 | [SerializeField] 13 | private List _values; 14 | 15 | public BetterDictionary() : base() { } 16 | public BetterDictionary(int capacity) : base(capacity) { } 17 | public BetterDictionary(IEqualityComparer comparer) : base(0, comparer) { } 18 | public BetterDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } 19 | public BetterDictionary(IDictionary dictionary) : base(dictionary, null) { } 20 | public BetterDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary?.Count ?? 0, comparer) { } 21 | 22 | void ISerializationCallbackReceiver.OnBeforeSerialize() 23 | { 24 | foreach (var value in Values) 25 | { 26 | if (value is ISerializationCallbackReceiver callback) callback.OnBeforeSerialize(); 27 | } 28 | _keys = new List(Keys); 29 | _values = new List(Values); 30 | } 31 | 32 | void ISerializationCallbackReceiver.OnAfterDeserialize() 33 | { 34 | int count = _keys.Count; 35 | Clear(); 36 | for (int i = 0; i < count; i++) { 37 | this[_keys[i]] = _values[i]; 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Engine/Helpers/BetterDictionary.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa6d0fa450654483956fc7fd15e238fc 3 | timeCreated: 1607521954 -------------------------------------------------------------------------------- /Engine/Helpers/TypeCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEngine; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | public class TypeCache where TStorage : Attribute 9 | { 10 | public delegate bool TypeCachePredicate(Type type, ref TStorage storage, out Type key); 11 | 12 | private bool _initialized; 13 | private readonly Dictionary _cache; 14 | private readonly TypeCachePredicate _predicate; 15 | 16 | public IEnumerable Names 17 | { 18 | get 19 | { 20 | ShouldBuildCache(); 21 | foreach (var type in _cache.Keys) 22 | { 23 | yield return type.FullName; 24 | } 25 | } 26 | } 27 | 28 | public IEnumerable All 29 | { 30 | get 31 | { 32 | ShouldBuildCache(); 33 | return _cache.Values; 34 | } 35 | } 36 | 37 | public TStorage this[Type key] 38 | { 39 | get 40 | { 41 | ShouldBuildCache(); 42 | return _cache[key]; 43 | } 44 | } 45 | 46 | public TypeCache(TypeCachePredicate predicate) 47 | { 48 | _initialized = false; 49 | _cache = new Dictionary(); 50 | _predicate = predicate; 51 | } 52 | 53 | public TStorage Get() => this[typeof(T)]; 54 | 55 | public bool TryGet(Type type, out TStorage output) 56 | { 57 | ShouldBuildCache(); 58 | return _cache.TryGetValue(type, out output); 59 | } 60 | 61 | private void ShouldBuildCache(bool force = false) 62 | { 63 | if (!_initialized || force) BuildCache(); 64 | } 65 | 66 | private void BuildCache() 67 | { 68 | foreach (var type in TypeExtensions.GetAllTypes(typeof(TSearch))) 69 | { 70 | foreach (var attribute in type.GetCustomAttributes()) 71 | { 72 | var storage = attribute; 73 | if (_predicate(type, ref storage, out var key)) 74 | { 75 | _cache.Add(key, storage); 76 | } 77 | } 78 | } 79 | 80 | _initialized = true; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Engine/Helpers/TypeCache.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c38391ad076e52b40ac03046fc103029 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Helpers/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Sirenix.OdinInspector.Editor; 6 | using Sirenix.Utilities; 7 | using UnityEngine; 8 | 9 | namespace RedOwl.Sleipnir.Engine 10 | { 11 | public static class TypeExtensions 12 | { 13 | private static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy; 14 | 15 | private static readonly Dictionary<(Type, Type), bool> CastableCache = new Dictionary<(Type, Type), bool>(); 16 | 17 | public static IEnumerable SafeGetTypes(this Assembly self) 18 | { 19 | try 20 | { 21 | return self.GetTypes(); 22 | } 23 | catch 24 | { 25 | return Type.EmptyTypes; 26 | } 27 | } 28 | 29 | public static string SafeGetName(this Type self) 30 | { 31 | string output = ""; 32 | var type = self; 33 | while (true) 34 | { 35 | output = type.FullName; 36 | if (string.IsNullOrEmpty(output)) 37 | { 38 | output = self.GetGenericTypeDefinition().FullName; 39 | if (!string.IsNullOrEmpty(output)) 40 | { 41 | return output; 42 | } 43 | } 44 | else 45 | { 46 | return output; 47 | } 48 | 49 | type = type.BaseType; 50 | } 51 | } 52 | 53 | public static IEnumerable GetAllTypes() 54 | { 55 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 56 | { 57 | if (assembly.GlobalAssemblyCache) continue; 58 | foreach (var type in assembly.SafeGetTypes()) 59 | { 60 | yield return type; 61 | } 62 | } 63 | } 64 | 65 | public static IEnumerable GetAllTypes(Type match) 66 | { 67 | foreach (var type in GetAllTypes()) 68 | { 69 | if (match.IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface) 70 | yield return type; 71 | } 72 | } 73 | 74 | public static IEnumerable GetAllTypes() => GetAllTypes(typeof(T)); 75 | 76 | public static IEnumerable GetMethods(T instance) 77 | { 78 | foreach (MethodInfo info in instance.GetType().GetMethods(flags)) 79 | { 80 | yield return info; 81 | } 82 | } 83 | 84 | public static IEnumerable GetFields(T instance) 85 | { 86 | foreach (FieldInfo info in instance.GetType().GetFields(flags)) 87 | { 88 | yield return info; 89 | } 90 | } 91 | 92 | public static bool IsCastableTo(this Type from, Type to, bool implicitly = false) 93 | { 94 | // Based on https://stackoverflow.com/a/22031364 95 | var key = (from, to); 96 | if (CastableCache.TryGetValue(key, out bool support)) 97 | { 98 | return support; 99 | } 100 | 101 | support = to.IsAssignableFrom(from) || from.HasCastDefined(to, implicitly) || typeof(string).IsAssignableFrom(to); 102 | CastableCache.Add(key, support); 103 | return support; 104 | } 105 | 106 | private static bool HasCastDefined(this Type from, Type to, bool implicitly) 107 | { 108 | if ((from.IsPrimitive || from.IsEnum) && (to.IsPrimitive || to.IsEnum)) 109 | { 110 | if (!implicitly) 111 | { 112 | return from == to || (from != typeof(bool) && to != typeof(bool)); 113 | } 114 | 115 | Type[][] typeHierarchy = { 116 | new[] { typeof(byte), typeof(sbyte), typeof(char) }, 117 | new[] { typeof(short), typeof(ushort) }, 118 | new[] { typeof(int), typeof(uint) }, 119 | new[] { typeof(long), typeof(ulong) }, 120 | new[] { typeof(float) }, 121 | new[] { typeof(double) } 122 | }; 123 | 124 | var lowerTypes = Enumerable.Empty(); 125 | foreach (var types in typeHierarchy) 126 | { 127 | if (types.Any(t => t == to)) 128 | { 129 | return lowerTypes.Any(t => t == from); 130 | } 131 | 132 | lowerTypes = lowerTypes.Concat(types); 133 | } 134 | 135 | return false; // IntPtr, UIntPtr, Enum, Boolean 136 | } 137 | 138 | return HasCastOperator(to, m => m.GetParameters()[0].ParameterType, _ => from, implicitly, false) 139 | || HasCastOperator(from, _ => to, m => m.ReturnType, implicitly, true); 140 | } 141 | 142 | private static bool HasCastOperator( 143 | IReflect type, Func baseType, Func derivedType, bool implicitly, bool lookInBase) 144 | { 145 | var bindingFlags = BindingFlags.Public | BindingFlags.Static 146 | | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly); 147 | 148 | return type.GetMethods(bindingFlags).Any( 149 | m => (m.Name == "op_Implicit" || (!implicitly && m.Name == "op_Explicit")) 150 | && baseType(m).IsAssignableFrom(derivedType(m)) 151 | ); 152 | } 153 | 154 | public static void WithAttr(this Type self, Action callback, bool inherit = true) where T : Attribute 155 | { 156 | var attrs = self.GetCustomAttributes(inherit); 157 | foreach (var item in attrs) 158 | { 159 | if (item is T attr) callback(attr); 160 | } 161 | } 162 | 163 | public static void WithAttr(this MemberInfo self, Action callback, bool inherit = true) where T : Attribute 164 | { 165 | var attrs = self.GetCustomAttributes(inherit); 166 | foreach (var item in attrs) 167 | { 168 | if (item is T attr) callback(attr); 169 | } 170 | } 171 | 172 | public static void WithAttr(this T self, Action callback, bool inhert = true) where TAttr : Attribute 173 | { 174 | self.GetType().WithAttr(callback, inhert); 175 | } 176 | 177 | public static bool TryGetAttr(this Type self, out T attr, bool inhert = true) where T : Attribute 178 | { 179 | var attrs = self.GetCustomAttributes(inhert); 180 | foreach (var item in attrs) 181 | { 182 | if (!(item is T a)) continue; 183 | attr = a; 184 | return true; 185 | } 186 | 187 | attr = null; 188 | return false; 189 | } 190 | 191 | /* 192 | public static void ForFieldWithType(this T self, Action callback) where TType : IPort 193 | { 194 | foreach (var info in GetFields(self)) 195 | { 196 | if (typeof(TType).IsAssignableFrom(info.FieldType)) callback(info, (TType) info.GetValue(self)); 197 | } 198 | } 199 | */ 200 | 201 | public static void ForFieldWithAttr(this T self, Action callback, bool inhert = true) where TAttr : Attribute 202 | { 203 | foreach (var info in GetFields(self)) 204 | { 205 | info.WithAttr((attr) => { callback(attr, info); }, inhert); 206 | } 207 | } 208 | 209 | public static void ForMethodWithAttr(this T self, Action callback, bool inhert = true) where TAttr : Attribute 210 | { 211 | foreach (var info in GetMethods(self)) 212 | { 213 | info.WithAttr((attr) => { callback(attr, info); }, inhert); 214 | } 215 | } 216 | 217 | public static Dictionary GetFieldTable(this Type self, BindingFlags bindingFlags) 218 | { 219 | var output = new Dictionary(); 220 | foreach (var info in self.GetFields(bindingFlags).OrderBy(field => field.MetadataToken)) 221 | { 222 | if (output.ContainsKey(info.Name)) continue; 223 | output.Add(info.Name, info); 224 | } 225 | 226 | return output; 227 | } 228 | 229 | public static Dictionary GetPropertyTable(this Type self, BindingFlags bindingFlags) 230 | { 231 | var output = new Dictionary(); 232 | foreach (var info in self.GetProperties(bindingFlags).OrderBy(field => field.MetadataToken)) 233 | { 234 | if (output.ContainsKey(info.Name)) continue; 235 | output.Add(info.Name, info); 236 | } 237 | 238 | return output; 239 | } 240 | 241 | public static Dictionary GetMethodTable(this Type self, BindingFlags bindingFlags) 242 | { 243 | var output = new Dictionary(); 244 | foreach (var info in self.GetMethods(bindingFlags).OrderBy(field => field.MetadataToken)) 245 | { 246 | if (output.ContainsKey(info.Name)) continue; 247 | output.Add(info.Name, info); 248 | } 249 | return output; 250 | } 251 | } 252 | } -------------------------------------------------------------------------------- /Engine/Helpers/TypeExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fb049139834421f90abba4b41a4d872 3 | timeCreated: 1606323690 -------------------------------------------------------------------------------- /Engine/Node.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87342d56778bfd445ac4ab47a83e7d69 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Engine/Node/Node.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | public interface INode 8 | { 9 | IGraph Graph { get; } 10 | string NodeId { get; } 11 | bool IsFlowRoot { get; } 12 | Rect NodeRect { get; set; } 13 | Vector2 NodePosition { get; set; } 14 | 15 | // TODO: Convert to KeyedCollection based on performance hit of using Dictionary.Values - which seems to create a ValueCollection every time? 16 | Dictionary ValueInPorts { get; } 17 | Dictionary ValueOutPorts { get; } 18 | Dictionary FlowInPorts { get; } 19 | Dictionary FlowOutPorts { get; } 20 | 21 | void Definition(IGraph graph); 22 | void MarkDirty(); 23 | void Initialize(ref IFlow flow); 24 | } 25 | 26 | [Serializable] 27 | public abstract class Node : INode 28 | { 29 | public IGraph Graph { get; private set; } 30 | 31 | #if ODIN_INSPECTOR 32 | [HideInInspector] 33 | #endif 34 | [SerializeField] 35 | private string nodeId = Guid.NewGuid().ToString(); 36 | 37 | public string NodeId => nodeId; 38 | 39 | #if ODIN_INSPECTOR 40 | [HideInInspector] 41 | #endif 42 | [SerializeField] 43 | private Rect nodeRect; 44 | 45 | public Rect NodeRect 46 | { 47 | get => nodeRect; 48 | set => nodeRect = value; 49 | } 50 | 51 | public Vector2 NodePosition 52 | { 53 | get => nodeRect.position; 54 | set => nodeRect.position = value; 55 | } 56 | 57 | public Dictionary ValueInPorts { get; protected set; } 58 | public Dictionary ValueOutPorts { get; protected set; } 59 | 60 | public Dictionary FlowInPorts { get; protected set; } 61 | public Dictionary FlowOutPorts { get; protected set; } 62 | 63 | #region Dirty 64 | 65 | public void MarkDirty() 66 | { 67 | // Debug.Log($"Marking Node '{this}' dirty!"); 68 | OnDirty(); 69 | foreach (var port in ValueOutPorts.Values) 70 | { 71 | port.MarkDirty(); 72 | } 73 | } 74 | 75 | protected virtual void OnDirty() {} 76 | 77 | #endregion 78 | 79 | #region Definition 80 | 81 | public void Definition(IGraph graph) 82 | { 83 | // TODO: Increase Performance by Implementing an "IsDirty" system 84 | Graph = graph; 85 | try // TODO: can we scope this better? 86 | { 87 | DefineValuePorts(); 88 | DefineFlowPorts(); 89 | OnDefinition(); 90 | } 91 | catch 92 | { 93 | Debug.LogWarning($"Failed to Define Node {GetType().FullName} | {NodeId}"); 94 | throw; 95 | } 96 | } 97 | 98 | private void DefineValuePorts() 99 | { 100 | ValueInPorts = new Dictionary(); 101 | ValueOutPorts = new Dictionary(); 102 | if (!NodeAttribute.Cache.TryGet(GetType(), out var data)) return; 103 | foreach (var valuePort in data.ValuePorts) 104 | { 105 | var port = valuePort.GetOrCreatePort(this); 106 | if (valuePort.GraphPort) 107 | { 108 | var graphPort = port.Clone(Graph); 109 | (valuePort.Direction == PortDirection.Input ? Graph.ValueOutPorts : Graph.ValueInPorts).Add(graphPort.Name, graphPort); 110 | } 111 | (valuePort.Direction == PortDirection.Input ? ValueInPorts : ValueOutPorts).Add(valuePort.Name, port); 112 | // Debug.Log($"{this} has Value Port '{valuePort.Name} | {valuePort.Direction}'"); 113 | } 114 | } 115 | 116 | private void DefineFlowPorts() 117 | { 118 | FlowInPorts = new Dictionary(); 119 | FlowOutPorts = new Dictionary(); 120 | if (!NodeAttribute.Cache.TryGet(GetType(), out var data)) return; 121 | foreach (var flowPort in data.FlowPorts) 122 | { 123 | var port = flowPort.GetOrCreatePort(this); 124 | if (flowPort.GraphPort) 125 | { 126 | var graphPort = port.Clone(Graph); 127 | (flowPort.Direction == PortDirection.Input ? Graph.FlowOutPorts : Graph.FlowInPorts).Add(graphPort.Name, graphPort); 128 | } 129 | (flowPort.Direction == PortDirection.Input ? FlowInPorts : FlowOutPorts).Add(flowPort.Name, port); 130 | // Debug.Log($"{this} has Flow Port '{flowPort.Name} | {flowPort.Direction}'"); 131 | } 132 | } 133 | 134 | protected virtual void OnDefinition() {} 135 | 136 | #endregion 137 | 138 | #region Initialization 139 | 140 | public bool IsFlowRoot => NodeAttribute.Cache.TryGet(GetType(), out var data) && data.IsFlowRoot; 141 | 142 | public void Initialize(ref IFlow flow) 143 | { 144 | try // TODO: can we scope this better? 145 | { 146 | InitializePorts(ref flow); 147 | OnInitialize(ref flow); 148 | } 149 | catch 150 | { 151 | Debug.LogWarning($"Failed to Initialize Node {this} | {NodeId}"); 152 | throw; 153 | } 154 | } 155 | 156 | private void InitializePorts(ref IFlow flow) 157 | { 158 | foreach (var valueIn in ValueInPorts.Values) 159 | { 160 | valueIn.Initialize(ref flow); 161 | } 162 | 163 | foreach (var valueOut in ValueOutPorts.Values) 164 | { 165 | valueOut.Initialize(ref flow); 166 | } 167 | 168 | foreach (var flowIn in FlowInPorts.Values) 169 | { 170 | flowIn.Initialize(ref flow); 171 | } 172 | 173 | foreach (var flowOut in FlowOutPorts.Values) 174 | { 175 | flowOut.Initialize(ref flow); 176 | } 177 | } 178 | 179 | protected virtual void OnInitialize(ref IFlow flow) {} 180 | 181 | #endregion 182 | 183 | public override string ToString() => $"{GetType().Name}({NodeId.Substring(0,8)})"; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Engine/Node/Node.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0cba482c950805444a1e0d32230a423e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Node/NodeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEngine; 6 | using UnityEngine.Scripting; 7 | 8 | namespace RedOwl.Sleipnir.Engine 9 | { 10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] 11 | public sealed class NodeAttribute : PreserveAttribute 12 | { 13 | public static readonly TypeCache Cache = new TypeCache((Type nodeType, ref NodeAttribute attribute, out Type key) => 14 | { 15 | attribute.Initialize(nodeType); 16 | key = nodeType; 17 | return true; 18 | }); 19 | 20 | private const BindingFlags BindingFlags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy; 21 | 22 | #region User 23 | 24 | /// 25 | /// Display name of the node. 26 | /// 27 | /// If not supplied, this will be inferred based on the class name. 28 | /// 29 | public string Name { get; set; } 30 | 31 | public string Tooltip { get; set; } 32 | 33 | /// 34 | /// Slash-delimited path to categorize this node in the search window. 35 | /// 36 | public string Path { get; set; } 37 | 38 | public HashSet Tags { get; } 39 | 40 | public bool Deletable { get; set; } = true; 41 | 42 | public bool Moveable { get; set; } = true; 43 | 44 | public bool IsFlowRoot { get; set; } = false; 45 | 46 | public float MinWidth { get; set; } = 50; 47 | 48 | public float MinHeight { get; set; } = 10; 49 | 50 | public Vector2 MinSize => new Vector2(MinWidth, MinHeight); 51 | 52 | #endregion 53 | 54 | #region Internal 55 | 56 | public Type Type { get; private set; } 57 | public List ValuePorts { get; private set; } 58 | 59 | public List FlowPorts { get; private set; } 60 | 61 | public Type EditorView { get; private set; } 62 | public Type RuntimeView { get; private set; } 63 | 64 | #endregion 65 | 66 | public NodeAttribute(params string[] tags) 67 | { 68 | Tags = new HashSet(tags); 69 | } 70 | 71 | #region Initialization 72 | 73 | private void Initialize(Type type) 74 | { 75 | Type = type; 76 | 77 | var methodTable = Type.GetMethodTable(BindingFlags); 78 | methodTable.Add(string.Empty, null); 79 | 80 | ExtractSettings(); 81 | ExtractValuePorts(methodTable); 82 | ExtractFlowPorts(methodTable); 83 | ExtractViews(); 84 | } 85 | 86 | private void ExtractSettings() 87 | { 88 | Name = string.IsNullOrEmpty(Name) ? Type.Name.Replace("Node", "").Replace(".", "/") : Name; 89 | Path = string.IsNullOrEmpty(Path) ? Type.Namespace?.Replace(".", "/") : Path; 90 | 91 | } 92 | 93 | private void ExtractValuePorts(Dictionary methodTable) 94 | { 95 | ValuePorts = new List(); 96 | // This OrderBy sorts the fields by the order they are defined in the code with subclass fields first 97 | foreach (var info in Type.GetFields(BindingFlags).OrderBy(field => field.MetadataToken)) 98 | { 99 | foreach (var attribute in info.GetCustomAttributes(true)) 100 | { 101 | attribute.SetInfo(info); 102 | attribute.SetCallbackInfo(methodTable[attribute.Callback]); 103 | // Debug.Log($"Extracting Value Port '{attribute.Name} {attribute.Direction}'"); 104 | ValuePorts.Add(attribute); 105 | } 106 | } 107 | } 108 | 109 | private void ExtractFlowPorts(Dictionary methodTable) 110 | { 111 | FlowPorts = new List(); 112 | // This OrderBy sorts the fields by the order they are defined in the code with subclass fields first 113 | foreach (var fieldInfo in Type.GetFields(BindingFlags).OrderBy(field => field.MetadataToken)) 114 | { 115 | foreach (var attribute in fieldInfo.GetCustomAttributes(true)) 116 | { 117 | attribute.SetInfo(fieldInfo); 118 | attribute.SetCallbackInfo(methodTable[attribute.Callback]); 119 | // Debug.Log($"Extracting Flow Port '{attribute.Name} {attribute.Direction}'"); 120 | FlowPorts.Add(attribute); 121 | } 122 | } 123 | } 124 | 125 | private void ExtractViews() 126 | { 127 | EditorView = NodeViewAttribute.EditorViews.TryGet(Type, out var editor) ? editor.ViewType : null; 128 | RuntimeView = NodeViewAttribute.RuntimeViews.TryGet(Type, out var runtime) ? runtime.ViewType : null; 129 | } 130 | 131 | // TODO: Allow for nodes to add ContextMenu items when right clicking on a node 132 | /* 133 | private Dictionary _contextMethods; 134 | 135 | public IReadOnlyDictionary ContextMethods => _contextMethods; 136 | 137 | private void ExtractContextMethods() 138 | { 139 | _contextMethods = new Dictionary(); 140 | foreach (var method in Type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) 141 | { 142 | var contextAttr = method.GetCustomAttribute(); 143 | if (contextAttr != null) 144 | { 145 | _contextMethods.Add(contextAttr, method); 146 | } 147 | } 148 | } 149 | */ 150 | 151 | #endregion 152 | } 153 | } -------------------------------------------------------------------------------- /Engine/Node/NodeAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4e0522cfd85d4ceebe61aafc904330ac 3 | timeCreated: 1606423117 -------------------------------------------------------------------------------- /Engine/Node/NodeView.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UIElements; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | public interface INodeView 7 | { 8 | INode Node { get; } 9 | bool IsMoveable { get; } 10 | VisualElement ValueInPortContainer { get; } 11 | VisualElement ValueOutPortContainer { get; } 12 | VisualElement FlowInPortContainer { get; } 13 | VisualElement FlowOutPortContainer { get; } 14 | 15 | void Initialize(INode node, NodeAttribute info); 16 | Rect GetPosition(); 17 | void SetPosition(Rect position); 18 | } 19 | 20 | public abstract class RuntimeNodeView : INodeView 21 | { 22 | public INode Node { get; } 23 | public bool IsMoveable { get; } 24 | public VisualElement ValueInPortContainer { get; } 25 | public VisualElement ValueOutPortContainer { get; } 26 | public VisualElement FlowInPortContainer { get; } 27 | public VisualElement FlowOutPortContainer { get; } 28 | 29 | public void Initialize(INode node, NodeAttribute info) 30 | { 31 | 32 | } 33 | 34 | public Rect GetPosition() 35 | { 36 | return new Rect(); 37 | } 38 | 39 | public void SetPosition(Rect position) 40 | { 41 | 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Engine/Node/NodeView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b52b03c0d1e74821af8f714910b08b2b 3 | timeCreated: 1609789845 -------------------------------------------------------------------------------- /Engine/Node/NodeViewAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine.Scripting; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 7 | public class NodeViewAttribute : PreserveAttribute 8 | { 9 | // These cache the INodeType => NodeViewAttribute based on if the attribute is on a custom INodeView class so that we can lookup the View by INodeType 10 | public static readonly TypeCache EditorViews = new TypeCache(EditorPredicate); 11 | public static readonly TypeCache RuntimeViews = new TypeCache(RuntimePredicate); 12 | 13 | public Type NodeType { get; } 14 | 15 | public bool IsRuntimeView { get; set; } 16 | 17 | public Type ViewType { get; private set; } 18 | 19 | public NodeViewAttribute(Type nodeType, bool isRuntimeView = false) 20 | { 21 | NodeType = nodeType; 22 | IsRuntimeView = isRuntimeView; 23 | } 24 | 25 | private static bool EditorPredicate(Type type, ref NodeViewAttribute attribute, out Type key) 26 | { 27 | attribute.ViewType = type; 28 | key = attribute.NodeType; 29 | return true; 30 | } 31 | 32 | private static bool RuntimePredicate(Type type, ref NodeViewAttribute attribute, out Type key) 33 | { 34 | attribute.ViewType = type; 35 | key = attribute.IsRuntimeView ? attribute.NodeType : null; 36 | return key != null; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Engine/Node/NodeViewAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: feaebac998514b1e9030af49bb1847b1 3 | timeCreated: 1609790083 -------------------------------------------------------------------------------- /Engine/Node/RequireNodeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | /// 7 | /// Required node for a given Graph. 8 | /// Will automatically instantiate the node when the graph is first created. 9 | /// 10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 11 | public sealed class RequireNodeAttribute : Attribute 12 | { 13 | public Type Type { get; set; } 14 | public Vector2 Position { get; set; } 15 | 16 | /// Type of the required node 17 | /// y position to creating 18 | /// x position to creating 19 | public RequireNodeAttribute(Type type, float x = 0, float y = 0) 20 | { 21 | Type = type; 22 | Position = new Vector2(x, y); 23 | 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Engine/Node/RequireNodeAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 80570dae483b4f3caf935e6c332a21ac 3 | timeCreated: 1606796957 -------------------------------------------------------------------------------- /Engine/Node/Types.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1241766aa6ba4bbf88da2511e336dafd 3 | timeCreated: 1610109242 -------------------------------------------------------------------------------- /Engine/Node/Types/Common.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: edfbe3f9d930427a9f5c32058818a393 3 | timeCreated: 1607357309 -------------------------------------------------------------------------------- /Engine/Node/Types/Common/KitchenSinkNode.cs: -------------------------------------------------------------------------------- 1 | // using System.Collections; 2 | // using UnityEngine; 3 | // 4 | // namespace RedOwl.UIX.Engine 5 | // { 6 | // [Node("Debug", Path = "Debug")] 7 | // public class KitchenSinkNode : Node 8 | // { 9 | // // Flow 10 | // [FlowIn(Callback = nameof(OnEnter))] public FlowPort Enter; 11 | // [FlowIn(Callback = nameof(OnEnter))] public FlowPort Passthrough; 12 | // 13 | // [FlowOut] public FlowPort Start; 14 | // [FlowOut] public FlowPort Changed; 15 | // [FlowOut] public FlowPort Complete; 16 | // 17 | // // Primatives 18 | // 19 | // [ValueIn, ValueOut] public ValuePort String; 20 | // [ValueIn, ValueOut] public ValuePort Char; 21 | // [ValueIn, ValueOut] public ValuePort Bool; 22 | // [ValueIn, ValueOut] public ValuePort Float; 23 | // [ValueIn, ValueOut] public ValuePort Double; 24 | // // [ValueIn, ValueOut] public ValuePort Decimal; // Does Not Serialize 25 | // [ValueIn, ValueOut] public ValuePort Short; 26 | // [ValueIn, ValueOut] public ValuePort Int; 27 | // [ValueIn, ValueOut] public ValuePort Long; 28 | // [ValueIn, ValueOut] public ValuePort UShort; 29 | // [ValueIn, ValueOut] public ValuePort UInt; 30 | // [ValueIn, ValueOut] public ValuePort ULong; 31 | // [ValueIn, ValueOut] public ValuePort SByte; 32 | // [ValueIn, ValueOut] public ValuePort Byte; 33 | // 34 | // // Array 35 | // // [ValueIn, ValueOut] public ValuePort StringArray; // Caused a Crash on Recompile 36 | // // [ValueIn, ValueOut] public ValuePort FloatArray; // Caused a Crash on Recompile 37 | // // 38 | // // // List 39 | // // [ValueIn, ValueOut] public ValuePort StringList; // Caused a Crash on Recompile 40 | // // [ValueIn, ValueOut] public ValuePort FloatList; // Caused a Crash on Recompile 41 | // 42 | // // Complex Types 43 | // // [ValueIn, ValueOut] public ValuePort Uri; // Does Not Serialize 44 | // // [ValueIn, ValueOut] public ValuePort Guid; // Does Not Serialize 45 | // // [ValueIn, ValueOut] public ValuePort DateTime; // Does Not Serialize 46 | // // [ValueIn, ValueOut] public ValuePort DateTimeOffset; // Does Not Serialize 47 | // // [ValueIn, ValueOut] public ValuePort TimeSpan; // Does Not Serialize 48 | // 49 | // // Unity Types 50 | // [ValueIn, ValueOut] public ValuePort Color; 51 | // [ValueIn, ValueOut] public ValuePort Color32; 52 | // [ValueIn, ValueOut] public ValuePort Vector2; 53 | // [ValueIn, ValueOut] public ValuePort Vector3; 54 | // [ValueIn, ValueOut] public ValuePort Vector4; 55 | // [ValueIn, ValueOut] public ValuePort Vector2Int; 56 | // [ValueIn, ValueOut] public ValuePort Vector3Int; 57 | // [ValueIn, ValueOut] public ValuePort Quaternion; 58 | // [ValueIn, ValueOut] public ValuePort Bounds; 59 | // [ValueIn, ValueOut] public ValuePort BoundsInt; 60 | // [ValueIn, ValueOut] public ValuePort Rect; 61 | // [ValueIn, ValueOut] public ValuePort RectInt; 62 | // // [ValueIn, ValueOut] public ValuePort AnimationCurve; // Caused a Crash on Recompile 63 | // // [ValueIn, ValueOut] public ValuePort Gradient; // Caused a Crash on Recompile 64 | // // [ValueIn, ValueOut] public ValuePort LayerMask; // Caused a Crash on Recompile 65 | // // [ValueIn, ValueOut] public ValuePort Texture; // Does Not Serialize 66 | // // [ValueIn, ValueOut] public ValuePort Texture2D; // Does Not Serialize 67 | // // [ValueIn, ValueOut] public ValuePort Texture3D; // Does Not Serialize 68 | // // [ValueIn, ValueOut] public ValuePort RenderTexture; // Does Not Serialize 69 | // 70 | // // Unity Mathamatics 71 | // 72 | // // Field 73 | // public string Field = "Test"; 74 | // 75 | // public KitchenSinkNode() 76 | // { 77 | // Enter = new FlowPort(this); 78 | // Passthrough = new FlowPort(this); 79 | // 80 | // Start = new FlowPort(this); 81 | // Changed = new FlowPort(this); 82 | // Complete = new FlowPort(this); 83 | // 84 | // String = new ValuePort(this); 85 | // Char = new ValuePort(this); 86 | // Bool = new ValuePort(this); 87 | // Float = new ValuePort(this); 88 | // Double = new ValuePort(this); 89 | // // Decimal = new ValuePort(this); 90 | // Short = new ValuePort(this); 91 | // Int = new ValuePort(this); 92 | // Long = new ValuePort(this); 93 | // UShort = new ValuePort(this); 94 | // UInt = new ValuePort(this); 95 | // ULong = new ValuePort(this); 96 | // SByte = new ValuePort(this); 97 | // Byte = new ValuePort(this); 98 | // 99 | // // StringArray = new ValuePort(this, new []{"Hello", "World"}); 100 | // // FloatArray = new ValuePort(this, new []{1.5f, 0.5f}); 101 | // // 102 | // // StringList = new ValuePort>(this, new List {"Hello", "World"}); 103 | // // FloatList = new ValuePort>(this, new List {1.5f, 0.5f}); 104 | // 105 | // // Uri = new ValuePort(this); 106 | // // Guid = new ValuePort(this); 107 | // // DateTime = new ValuePort(this); 108 | // // DateTimeOffset = new ValuePort(this); 109 | // // TimeSpan = new ValuePort(this); 110 | // 111 | // Color = new ValuePort(this); 112 | // Color32 = new ValuePort(this); 113 | // Vector2 = new ValuePort(this); 114 | // Vector3 = new ValuePort(this); 115 | // Vector4 = new ValuePort(this); 116 | // Vector2Int = new ValuePort(this); 117 | // Vector3Int = new ValuePort(this); 118 | // Quaternion = new ValuePort(this); 119 | // Bounds = new ValuePort(this); 120 | // BoundsInt = new ValuePort(this); 121 | // Rect = new ValuePort(this); 122 | // RectInt = new ValuePort(this); 123 | // // AnimationCurve = new ValuePort(this); 124 | // // Gradient = new ValuePort(this); 125 | // // LayerMask = new ValuePort(this); 126 | // // Texture = new ValuePort(this); 127 | // // Texture2D = new ValuePort(this); 128 | // // Texture3D = new ValuePort(this); 129 | // // RenderTexture = new ValuePort(this); 130 | // 131 | // // Unity Mathamatics 132 | // } 133 | // 134 | // public override void Definition() 135 | // { 136 | // 137 | // } 138 | // 139 | // private IEnumerable OnEnter(IFlow flow) 140 | // { 141 | // yield return Start; 142 | // 143 | // for (int i = 0; i < 5; i++) 144 | // { 145 | // yield return Changed; 146 | // yield return new WaitForSeconds(0.5f); 147 | // } 148 | // 149 | // yield return Complete; 150 | // } 151 | // 152 | // private IEnumerable OnPassthrough(IFlow flow) 153 | // { 154 | // yield return Complete; 155 | // } 156 | // } 157 | // } 158 | -------------------------------------------------------------------------------- /Engine/Node/Types/Common/KitchenSinkNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec325f35bd8041f7ab2cbe10da760495 3 | timeCreated: 1608037941 -------------------------------------------------------------------------------- /Engine/Node/Types/Common/LogNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | [Node("Common", Path = "Common")] 6 | public class LogNode : Node 7 | { 8 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 9 | 10 | [ValueIn] public ValuePort Message = ""; 11 | 12 | private void OnEnter(IFlow flow) => Debug.Log(Message.Value); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Engine/Node/Types/Common/LogNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5288c29e0e62447c80f22f98344141ac 3 | timeCreated: 1607357328 -------------------------------------------------------------------------------- /Engine/Node/Types/Common/ValueNodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sirenix.OdinInspector; 3 | using UnityEngine; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | #region Base 8 | 9 | [Serializable] 10 | [Node("Common", Path = "Common/Value")] 11 | public abstract class ValueNode : Node 12 | { 13 | [ValueOut] public ValuePort Value; 14 | 15 | [SerializeField, OnValueChanged("MarkDirty")] 16 | private TValue defaultValue; 17 | protected ValueNode(TValue defaultValue = default) 18 | { 19 | this.defaultValue = defaultValue; 20 | } 21 | 22 | protected override void OnDefinition() 23 | { 24 | Value.SetDefault(defaultValue); 25 | } 26 | 27 | protected override void OnInitialize(ref IFlow flow) 28 | { 29 | Value.SetDefault(defaultValue); 30 | } 31 | } 32 | 33 | #endregion 34 | 35 | #region Primatives 36 | 37 | public class StringValueNode : ValueNode 38 | { 39 | public StringValueNode() : this(default) {} 40 | public StringValueNode(string defaultValue) : base(defaultValue) { } 41 | } 42 | 43 | public class CharValueNode : ValueNode 44 | { 45 | public CharValueNode() : this(default) {} 46 | public CharValueNode(char defaultValue) : base(defaultValue) { } 47 | } 48 | 49 | public class BoolValueNode : ValueNode 50 | { 51 | public BoolValueNode() : this(default) {} 52 | public BoolValueNode(bool defaultValue) : base(defaultValue) { } 53 | } 54 | 55 | public class FloatValueNode : ValueNode 56 | { 57 | public FloatValueNode() : this(default) {} 58 | public FloatValueNode(float defaultValue) : base(defaultValue) { } 59 | } 60 | 61 | public class DoubleValueNode : ValueNode 62 | { 63 | public DoubleValueNode() : this(default) {} 64 | public DoubleValueNode(double defaultValue) : base(defaultValue) { } 65 | } 66 | 67 | public class ShortValueNode : ValueNode 68 | { 69 | public ShortValueNode() : this(default) {} 70 | public ShortValueNode(short defaultValue) : base(defaultValue) { } 71 | } 72 | 73 | public class IntValueNode : ValueNode 74 | { 75 | public IntValueNode() : this(default) {} 76 | public IntValueNode(int defaultValue) : base(defaultValue) { } 77 | } 78 | 79 | public class LongValueNode : ValueNode 80 | { 81 | public LongValueNode() : this(default) {} 82 | public LongValueNode(long defaultValue) : base(defaultValue) { } 83 | } 84 | 85 | public class UShortValueNode : ValueNode 86 | { 87 | public UShortValueNode() : this(default) {} 88 | public UShortValueNode(ushort defaultValue) : base(defaultValue) { } 89 | } 90 | 91 | public class UIntValueNode : ValueNode 92 | { 93 | public UIntValueNode() : this(default) {} 94 | public UIntValueNode(uint defaultValue) : base(defaultValue) { } 95 | } 96 | 97 | public class ULongValueNode : ValueNode 98 | { 99 | public ULongValueNode() : this(default) {} 100 | public ULongValueNode(ulong defaultValue) : base(defaultValue) { } 101 | } 102 | 103 | public class SByteValueNode : ValueNode 104 | { 105 | public SByteValueNode() : this(default) {} 106 | public SByteValueNode(sbyte defaultValue) : base(defaultValue) { } 107 | } 108 | 109 | public class ByteValueNode : ValueNode 110 | { 111 | public ByteValueNode() : this(default) {} 112 | public ByteValueNode(byte defaultValue) : base(defaultValue) { } 113 | } 114 | 115 | #endregion 116 | 117 | #region Unity 118 | 119 | public class ColorValueNode : ValueNode 120 | { 121 | public ColorValueNode() : this(default) {} 122 | public ColorValueNode(Color defaultValue) : base(defaultValue) { } 123 | } 124 | 125 | public class Color32ValueNode : ValueNode 126 | { 127 | public Color32ValueNode() : this(default) {} 128 | public Color32ValueNode(Color32 defaultValue) : base(defaultValue) { } 129 | } 130 | 131 | public class Vector2ValueNode : ValueNode 132 | { 133 | public Vector2ValueNode() : this(default) {} 134 | public Vector2ValueNode(Vector2 defaultValue) : base(defaultValue) { } 135 | } 136 | 137 | public class Vector3ValueNode : ValueNode 138 | { 139 | public Vector3ValueNode() : this(default) {} 140 | public Vector3ValueNode(Vector3 defaultValue) : base(defaultValue) { } 141 | } 142 | 143 | public class Vector4ValueNode : ValueNode 144 | { 145 | public Vector4ValueNode() : this(default) {} 146 | public Vector4ValueNode(Vector4 defaultValue) : base(defaultValue) { } 147 | } 148 | 149 | public class Vector2IntValueNode : ValueNode 150 | { 151 | public Vector2IntValueNode() : this(default) {} 152 | public Vector2IntValueNode(Vector2Int defaultValue) : base(defaultValue) { } 153 | } 154 | 155 | public class Vector3IntValueNode : ValueNode 156 | { 157 | public Vector3IntValueNode() : this(default) {} 158 | public Vector3IntValueNode(Vector3Int defaultValue) : base(defaultValue) { } 159 | } 160 | 161 | public class QuaternionValueNode : ValueNode 162 | { 163 | public QuaternionValueNode() : this(default) {} 164 | public QuaternionValueNode(Quaternion defaultValue) : base(defaultValue) { } 165 | } 166 | 167 | public class BoundsValueNode : ValueNode 168 | { 169 | public BoundsValueNode() : this(default) {} 170 | public BoundsValueNode(Bounds defaultValue) : base(defaultValue) { } 171 | } 172 | 173 | public class BoundsIntValueNode : ValueNode 174 | { 175 | public BoundsIntValueNode() : this(default) {} 176 | public BoundsIntValueNode(BoundsInt defaultValue) : base(defaultValue) { } 177 | } 178 | 179 | public class RectValueNode : ValueNode 180 | { 181 | public RectValueNode() : this(default) {} 182 | public RectValueNode(Rect defaultValue) : base(defaultValue) { } 183 | } 184 | 185 | public class RectIntValueNode : ValueNode 186 | { 187 | public RectIntValueNode() : this(default) {} 188 | public RectIntValueNode(RectInt defaultValue) : base(defaultValue) { } 189 | } 190 | 191 | #endregion 192 | } -------------------------------------------------------------------------------- /Engine/Node/Types/Common/ValueNodes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 911cf2e307e04f3fb0c9324f3e98a369 3 | timeCreated: 1608213448 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 675d668076e04065aa4c2c280998bcea 3 | timeCreated: 1607354703 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/BranchNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control")] 4 | public class BranchNode : Node 5 | { 6 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 7 | 8 | [FlowOut] public FlowPort True; 9 | [FlowOut] public FlowPort False; 10 | 11 | [ValueIn] public ValuePort Condition = false; 12 | 13 | private IFlowPort OnEnter(IFlow flow) 14 | { 15 | return Condition.Value ? True : False; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/BranchNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 841f81a71e1d4d61a59cc599e2fdfb71 3 | timeCreated: 1607354703 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/CoroutineNode.cs: -------------------------------------------------------------------------------- 1 | // namespace RedOwl.UIX.Engine 2 | // { 3 | // [Node("Flow", Path = "Flow Control")] 4 | // public class CoroutineNode : Node 5 | // { 6 | // [ValueIn] public float Interval { get; } = 1; 7 | // 8 | // [FlowIn] 9 | // private void Enter(Flow flow) 10 | // { 11 | // flow.StartRoutine(this, Exit, flow.Get(Interval)); 12 | // } 13 | // 14 | // [FlowOut] private ControlPort Exit; 15 | // } 16 | // } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/CoroutineNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9c2a873bcab41f5988238b2d4695224 3 | timeCreated: 1607359620 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/EnterNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control", IsFlowRoot = true)] 4 | public class EnterNode : Node 5 | { 6 | [FlowOut(GraphPort = true)] public FlowPort Enter; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/EnterNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2bca0ecb9b364d4db95c2daa26e5d3f2 3 | timeCreated: 1610134896 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/ExitNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control")] 4 | public class ExitNode : Node 5 | { 6 | [FlowIn(GraphPort = true)] public FlowPort Exit; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/ExitNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 85ad7c94bee444028de813c6d072f580 3 | timeCreated: 1610134943 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/FixedUpdateNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control", IsFlowRoot = true)] 4 | public class FixedUpdateNode : Node 5 | { 6 | [FlowOut(GraphPort = true)] public FlowPort FixedUpdate; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/FixedUpdateNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 14a5bcf538134cc587739ce09b8c2c1e 3 | timeCreated: 1609014236 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/LateUpdateNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control", IsFlowRoot = true)] 4 | public class LateUpdateNode : Node 5 | { 6 | [FlowOut(GraphPort = true)] public FlowPort LateUpdate; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/LateUpdateNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 04dd3d41f0b6442cac3bcfb82db22e04 3 | timeCreated: 1609014272 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/LoopNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | [Node("Flow", Path = "Flow Control")] 6 | public class LoopNode : Node 7 | { 8 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 9 | [FlowOut] public FlowPort Exit; 10 | 11 | [ValueIn] public ValuePort Count; 12 | 13 | private IEnumerator OnEnter(IFlow flow) 14 | { 15 | int times = 0; 16 | while (times < Count.Value) 17 | { 18 | yield return Exit; 19 | times += 1; 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/LoopNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 11ff7c8bb4af48e2ba0fbedc31f8cadc 3 | timeCreated: 1609188546 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/StartNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control", IsFlowRoot = true)] 4 | public class StartNode : Node 5 | { 6 | [FlowOut(GraphPort = true)] public FlowPort Start; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/StartNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7bd2d564934b47a19d0de4cfc6a324ea 3 | timeCreated: 1607197475 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/TypeWriterNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | [Node("Flow", Path = "Flow Control")] 7 | public class TypeWriterNode : Node 8 | { 9 | [FlowIn(Callback = nameof(WriteText))] public FlowPort Start; 10 | [FlowOut] public FlowPort Typed; 11 | [FlowOut] public FlowPort Complete; 12 | 13 | [ValueIn] public ValuePort Text = "Hello World"; 14 | [ValueIn] public ValuePort Delay = 0.2f; 15 | [ValueOut] public ValuePort Current = ""; 16 | 17 | private IEnumerable WriteText(IFlow flow) 18 | { 19 | string startingText = Text.Value; 20 | float wait = Delay.Value; 21 | int count = 1; 22 | 23 | while (Current.Value.Length < startingText.Length) 24 | { 25 | Current.Value = startingText.Substring(0, count); 26 | 27 | yield return Typed; 28 | 29 | yield return new WaitForSeconds(wait); 30 | 31 | count++; 32 | } 33 | 34 | yield return Complete; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/TypeWriterNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aeb3aa64cd16481aa5135e9bbe163c94 3 | timeCreated: 1609593273 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/UpdateNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | [Node("Flow", Path = "Flow Control", IsFlowRoot = true)] 4 | public class UpdateNode : Node 5 | { 6 | [FlowOut(GraphPort = true)] public FlowPort Update; 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/UpdateNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7902e6c114cc485797d3b51e7e5ef41a 3 | timeCreated: 1607215164 -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/WhileNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | [Node("Flow", Path = "Flow Control")] 6 | public class WhileNode : Node 7 | { 8 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 9 | [FlowOut] public FlowPort True; 10 | [FlowOut] public FlowPort Exit; 11 | 12 | [ValueIn] public ValuePort Condition = true; 13 | 14 | private IEnumerator OnEnter(IFlow flow) 15 | { 16 | while (Condition.Value) 17 | { 18 | yield return True; 19 | } 20 | 21 | yield return Exit; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Engine/Node/Types/FlowNodes/WhileNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5bf0fd20e6a74aec8fb4fde739fa119d 3 | timeCreated: 1607359427 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae667c284d184845b609d07bc9c0a563 3 | timeCreated: 1607357033 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/AndNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public class AndNode : MathNode 4 | { 5 | protected override void Calculate(IFlow flow) 6 | { 7 | Output.Value = Left.Value && Right.Value; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/AndNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d930092407d2432ea737ed7d69ff23d5 3 | timeCreated: 1608235890 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/ComparisonNode.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public class ComparisonNode : MathNode 6 | { 7 | public enum Comparison 8 | { 9 | Equal, 10 | NotEqual, 11 | GreaterThan, 12 | GreaterThanOrEqual, 13 | LessThan, 14 | LessThenOrEqual 15 | } 16 | 17 | public Comparison comparison = Comparison.Equal; 18 | 19 | protected override void Calculate(IFlow flow) 20 | { 21 | switch (comparison) 22 | { 23 | case Comparison.Equal: 24 | Output.Value = Approximately(Left.Value, Right.Value); 25 | break; 26 | case Comparison.NotEqual: 27 | Output.Value = !Approximately(Left.Value, Right.Value); 28 | break; 29 | case Comparison.GreaterThan: 30 | Output.Value = Left.Value > Right.Value; 31 | break; 32 | case Comparison.GreaterThanOrEqual: 33 | Output.Value = Left.Value >= Right.Value; 34 | break; 35 | case Comparison.LessThan: 36 | Output.Value = Left.Value < Right.Value; 37 | break; 38 | case Comparison.LessThenOrEqual: 39 | Output.Value = Left.Value <= Right.Value; 40 | break; 41 | } 42 | } 43 | 44 | private bool Approximately(double a, double b) => math.abs(b - a) < math.EPSILON_DBL; 45 | } 46 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/ComparisonNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67e2584b0e8840dc8e42d559887ebb85 3 | timeCreated: 1608234389 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/MathNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | [Node("Math", Path = "Math")] 6 | public abstract class MathNode : Node 7 | { 8 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 9 | [FlowOut] public FlowPort Exit; 10 | 11 | [ValueIn] public ValuePort Input; 12 | 13 | [ValueOut] public ValuePort Output; 14 | 15 | protected void OnEnter(IFlow flow) 16 | { 17 | Calculate(flow); 18 | } 19 | 20 | protected abstract void Calculate(IFlow flow); 21 | } 22 | 23 | [Node("Math", Path = "Math")] 24 | public abstract class MathNode : Node 25 | { 26 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 27 | [FlowOut] public FlowPort Exit; 28 | 29 | [ValueIn] public ValuePort Left; 30 | [ValueIn] public ValuePort Right; 31 | 32 | [ValueOut] public ValuePort Output; 33 | 34 | protected IFlowPort OnEnter(IFlow flow) 35 | { 36 | Calculate(flow); 37 | return Exit; 38 | } 39 | 40 | protected abstract void Calculate(IFlow flow); 41 | } 42 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/MathNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 587e3ef3753348d4a4b15a4cb7708a58 3 | timeCreated: 1607290831 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/NotNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public class NotNode : MathNode 4 | { 5 | protected override void Calculate(IFlow flow) 6 | { 7 | Output.Value = !Input.Value; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/NotNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c71b85452b80424d9605d6f9f6e741fc 3 | timeCreated: 1608235740 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/OperationNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public class OperationNode : MathNode 6 | { 7 | public enum Operation 8 | { 9 | Add, 10 | Subtract, 11 | Multiply, 12 | Divide, 13 | } 14 | 15 | public Operation operation = Operation.Add; 16 | 17 | protected override void Calculate(IFlow flow) 18 | { 19 | switch (operation) 20 | { 21 | case Operation.Add: 22 | Debug.Log($"Doing Calculation '{Left.Value} + {Right.Value}'"); 23 | Output.Value = Left.Value + Right.Value; 24 | break; 25 | case Operation.Subtract: 26 | Output.Value = Left.Value - Right.Value; 27 | break; 28 | case Operation.Multiply: 29 | Output.Value = Left.Value * Right.Value; 30 | break; 31 | case Operation.Divide: 32 | var rhs = Right.Value; 33 | Output.Value = Left.Value / rhs == 0 ? 1 : rhs; 34 | break; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/OperationNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7335d35b93294fb491b9f9b392e73785 3 | timeCreated: 1608234100 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/OrNode.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public class OrNode : MathNode 4 | { 5 | protected override void Calculate(IFlow flow) 6 | { 7 | Output.Value = Left.Value || Right.Value; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/OrNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4d86a673dd4d440fa1598a8ec7b6541f 3 | timeCreated: 1608235824 -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/SignNode.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public class SignNode : MathNode 6 | { 7 | protected override void Calculate(IFlow flow) 8 | { 9 | Output.Value = math.sign(Input.Value); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Engine/Node/Types/MathNodes/SignNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae7972aac9dd4156aabfab7cc558af00 3 | timeCreated: 1608235561 -------------------------------------------------------------------------------- /Engine/Node/Types/StateMachine.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bebcc41c3687477a967f312e68f77fdc 3 | timeCreated: 1607811795 -------------------------------------------------------------------------------- /Engine/Node/Types/StateMachine/StateNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public abstract class StateNode : Node 6 | { 7 | [FlowIn(nameof(HandleEnter))] public FlowPort Enter; 8 | [FlowOut(nameof(OnExit))] public FlowPort Exit; 9 | 10 | protected IEnumerator HandleEnter(IFlow flow) 11 | { 12 | OnEnter(flow); 13 | var enumerator = OnUpdate(flow); 14 | while (enumerator.MoveNext()) 15 | { 16 | yield return enumerator.Current; 17 | } 18 | 19 | yield return Exit; 20 | } 21 | 22 | protected abstract void OnEnter(IFlow flow); 23 | protected abstract IEnumerator OnUpdate(IFlow flow); 24 | protected abstract void OnExit(IFlow flow); 25 | } 26 | } -------------------------------------------------------------------------------- /Engine/Node/Types/StateMachine/StateNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd0ccc987cec4a2fafae8b6cd4eefcc8 3 | timeCreated: 1607811804 -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 058ad88da9f44663960c379f397d9eec 3 | timeCreated: 1609852277 -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphFlowPortNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | public interface IGraphFlowPortNode 7 | { 8 | string Name { get; } 9 | } 10 | 11 | [Serializable] 12 | [Node("Common", Path = "Common/Graph Ports", IsFlowRoot = true)] 13 | public class FlowInNode : Node, IGraphFlowPortNode 14 | { 15 | [FlowOut(GraphPort = true)] public FlowPort Out; 16 | 17 | [SerializeField, HideInInspector] 18 | private string name = "In"; 19 | 20 | [ShowInNode] 21 | public string Name 22 | { 23 | get => name; 24 | set 25 | { 26 | name = value; 27 | // IsDefined = false; 28 | } 29 | } 30 | } 31 | 32 | [Serializable] 33 | [Node("Common", Path = "Common/Graph Ports")] 34 | public class FlowOutNode : Node, IGraphFlowPortNode 35 | { 36 | [FlowIn(GraphPort = true)] public FlowPort In; 37 | 38 | [SerializeField, HideInInspector] 39 | private string name = "Out"; 40 | 41 | [ShowInNode] 42 | public string Name 43 | { 44 | get => name; 45 | set 46 | { 47 | name = value; 48 | // IsDefined = false; 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphFlowPortNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9afb4c4560d54df793e464ce188966ef 3 | timeCreated: 1610489982 -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphReferenceNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | [Serializable] 8 | [Node("Common", Path = "Common", Tooltip = "Nest a Graph that comes from a Graph Asset")] 9 | public class GraphReferenceNode : INode 10 | { 11 | [SerializeField, HideInInspector] 12 | private GraphAsset asset; 13 | 14 | [ShowInNode] 15 | public GraphAsset Asset 16 | { 17 | get 18 | { 19 | if (asset == null) 20 | { 21 | // TODO: Need to Trigger Redraw of NodeView 22 | asset = ScriptableObject.CreateInstance(); 23 | asset.Graph = new Graph(); 24 | } 25 | return asset; 26 | } 27 | set 28 | { 29 | if (asset.Graph == null) asset.Graph = new Graph(); 30 | asset = value; 31 | // TODO: Need to Trigger Redraw of NodeView to get updated Ports 32 | } 33 | } 34 | 35 | #region INode 36 | 37 | public IGraph Graph => Asset.Graph.Graph; 38 | public string NodeId => Asset.Graph.NodeId; 39 | public bool IsFlowRoot => false; 40 | public Rect NodeRect 41 | { 42 | get => Asset.Graph.NodeRect; 43 | set 44 | { 45 | #if UNITY_EDITOR 46 | UnityEditor.EditorUtility.SetDirty(Asset); 47 | #endif 48 | Asset.Graph.NodeRect = value; 49 | } 50 | } 51 | 52 | public Vector2 NodePosition 53 | { 54 | get => Asset.Graph.NodePosition; 55 | set 56 | { 57 | #if UNITY_EDITOR 58 | UnityEditor.EditorUtility.SetDirty(Asset); 59 | #endif 60 | Asset.Graph.NodePosition = value; 61 | } 62 | } 63 | 64 | public Dictionary ValueInPorts => Asset.Graph.ValueInPorts; 65 | public Dictionary ValueOutPorts => Asset.Graph.ValueOutPorts; 66 | public Dictionary FlowInPorts => Asset.Graph.FlowInPorts; 67 | public Dictionary FlowOutPorts => Asset.Graph.FlowOutPorts; 68 | public void Definition(IGraph graph) => Asset.Graph.Definition(graph); 69 | public void MarkDirty() => Asset.Graph.MarkDirty(); 70 | public void Initialize(ref IFlow flow) => Asset.Graph.Initialize(ref flow); 71 | 72 | #endregion 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphReferenceNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7e82160be6f440283ea361cdc597af2 3 | timeCreated: 1607659170 -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphValuePortNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | #region Base 7 | 8 | public interface IGraphValuePortNode 9 | { 10 | string Name { get; } 11 | } 12 | 13 | [Serializable] 14 | [Node("Common", Path = "Common/Graph Ports")] 15 | public abstract class ValueInNode : Node, IGraphValuePortNode 16 | { 17 | [ValueOut(GraphPort = true)] public ValuePort Out = default; 18 | 19 | [SerializeField, HideInInspector] 20 | private string name = $"In {typeof(T).Name}"; 21 | 22 | [ShowInNode] 23 | public string Name 24 | { 25 | get => name; 26 | set 27 | { 28 | name = value; 29 | // IsDefined = false; 30 | } 31 | } 32 | } 33 | 34 | [Serializable] 35 | [Node("Common", Path = "Common/Graph Ports")] 36 | public abstract class ValueOutNode : Node, IGraphValuePortNode 37 | { 38 | [ValueIn(GraphPort = true)] public ValuePort In = default; 39 | 40 | [SerializeField, HideInInspector] 41 | private string name = $"Out {typeof(T).Name}"; 42 | 43 | [ShowInNode] 44 | public string Name 45 | { 46 | get => name; 47 | set 48 | { 49 | name = value; 50 | // IsDefined = false; 51 | } 52 | } 53 | } 54 | 55 | #endregion 56 | 57 | #region Primatives 58 | 59 | public class StringInNode : ValueInNode {} 60 | public class StringOutNode : ValueOutNode {} 61 | 62 | public class CharInNode : ValueInNode {} 63 | public class CharOutNode : ValueOutNode {} 64 | 65 | public class BoolInNode : ValueInNode {} 66 | public class BoolOutNode : ValueOutNode {} 67 | 68 | public class FloatInNode : ValueInNode {} 69 | public class FloatOutNode : ValueOutNode {} 70 | 71 | public class DoubleInNode : ValueInNode {} 72 | public class DoubleOutNode : ValueOutNode {} 73 | 74 | public class ShortInNode : ValueInNode {} 75 | public class ShortOutNode : ValueOutNode {} 76 | 77 | public class IntInNode : ValueInNode {} 78 | public class IntOutNode : ValueOutNode {} 79 | 80 | public class LongInNode : ValueInNode {} 81 | public class LongOutNode : ValueOutNode {} 82 | 83 | public class UShortInNode : ValueInNode {} 84 | public class UShortOutNode : ValueOutNode {} 85 | 86 | public class UIntInNode : ValueInNode {} 87 | public class UIntOutNode : ValueOutNode {} 88 | 89 | public class ULongInNode : ValueInNode {} 90 | public class ULongOutNode : ValueOutNode {} 91 | 92 | public class SByteInNode : ValueInNode {} 93 | public class SByteOutNode : ValueOutNode {} 94 | 95 | public class ByteInNode : ValueInNode {} 96 | public class ByteOutNode : ValueOutNode {} 97 | 98 | #endregion 99 | 100 | #region Unity 101 | 102 | public class ColorInNode : ValueInNode {} 103 | public class ColorOutNode : ValueOutNode {} 104 | 105 | public class Color32InNode : ValueInNode {} 106 | public class Color32OutNode : ValueOutNode {} 107 | 108 | public class Vector2InNode : ValueInNode {} 109 | public class Vector2OutNode : ValueOutNode {} 110 | 111 | public class Vector3InNode : ValueInNode {} 112 | public class Vector3OutNode : ValueOutNode {} 113 | 114 | public class Vector4InNode : ValueInNode {} 115 | public class Vector4OutNode : ValueOutNode {} 116 | 117 | public class Vector2IntInNode : ValueInNode {} 118 | public class Vector2IntOutNode : ValueOutNode {} 119 | 120 | public class Vector3IntInNode : ValueInNode {} 121 | public class Vector3IntOutNode : ValueOutNode {} 122 | 123 | public class QuaternionInNode : ValueInNode {} 124 | public class QuaternionOutNode : ValueOutNode {} 125 | 126 | public class BoundsInNode : ValueInNode {} 127 | public class BoundsOutNode : ValueOutNode {} 128 | 129 | public class BoundsIntInNode : ValueInNode {} 130 | public class BoundsIntOutNode : ValueOutNode {} 131 | 132 | public class RectInNode : ValueInNode {} 133 | public class RectOutNode : ValueOutNode {} 134 | 135 | public class RectIntInNode : ValueInNode {} 136 | public class RectIntOutNode : ValueOutNode {} 137 | 138 | #endregion 139 | } -------------------------------------------------------------------------------- /Engine/Node/Types/SubGraph/GraphValuePortNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff94cefa078146beb72cdb183727b05b 3 | timeCreated: 1610489957 -------------------------------------------------------------------------------- /Engine/Port.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e3496de9fc84678a2103c0233e3cadf 3 | timeCreated: 1610108562 -------------------------------------------------------------------------------- /Engine/Port/FlowPort.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | using UnityEngine.Scripting; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | public interface IFlowPort : IPort 9 | { 10 | void Definition(INode node, IFlowPortAttribute info); 11 | IFlowPort Clone(IGraph graph); 12 | IEnumerable Execute(); 13 | 14 | } 15 | 16 | [Preserve] 17 | public class FlowPort : Port, IFlowPort 18 | { 19 | private static readonly Type VoidType = typeof(void); 20 | private static readonly Type FlowType = typeof(IFlow); 21 | private static readonly Type FlowPortType = typeof(IFlowPort); 22 | private static readonly Type EnumerableType = typeof(IEnumerable); 23 | private static readonly Type SimpleCallbackType = typeof(Action); 24 | private static readonly Type SyncCallbackType = typeof(Func); 25 | private static readonly Type AsyncCallbackType = typeof(Func); 26 | 27 | public enum CallbackTypes 28 | { 29 | None, 30 | Simple, 31 | Sync, 32 | Async, 33 | } 34 | 35 | private IFlowPort Linked { get; set; } 36 | 37 | private CallbackTypes _callbackType; 38 | private Action _simpleCallback; 39 | private Func _syncCallback; 40 | private Func _asyncCallback; 41 | 42 | [Preserve] 43 | public FlowPort() 44 | { 45 | ValueType = FlowPortType; 46 | } 47 | 48 | public void Definition(INode node, IFlowPortAttribute info) 49 | { 50 | Id = new PortId(node.NodeId, info.Name); 51 | Node = node; 52 | Name = info.Name; 53 | Direction = info.Direction; 54 | Capacity = info.Capacity; 55 | GraphPort = info.GraphPort; 56 | if (info.CallbackInfo == null) return; 57 | var parameters = info.CallbackInfo.GetParameters(); 58 | if (parameters.Length != 1) 59 | { 60 | Debug.LogWarning($"FlowPort Callback for '{node}.{info.CallbackInfo.Name}' has {parameters.Length} parameter(s). Can only accept 1 parameter of type 'IFlow'"); 61 | return; 62 | } 63 | 64 | var paramType = parameters[0].ParameterType; 65 | if (paramType != FlowType) 66 | { 67 | Debug.LogWarning($"FlowPort Callback for '{node}.{info.CallbackInfo.Name}' has 1 parameter that takes type '{paramType}'. Can only accept 1 parameter of type 'IFlow'"); 68 | return; 69 | } 70 | if (VoidType.IsAssignableFrom(info.CallbackInfo.ReturnType)) 71 | { 72 | _simpleCallback = (Action)info.CallbackInfo.CreateDelegate(SimpleCallbackType, node); 73 | _callbackType = CallbackTypes.Simple; 74 | } 75 | if (FlowPortType.IsAssignableFrom(info.CallbackInfo.ReturnType)) 76 | { 77 | _syncCallback = (Func)info.CallbackInfo.CreateDelegate(SyncCallbackType, node); 78 | _callbackType = CallbackTypes.Sync; 79 | } 80 | if (EnumerableType.IsAssignableFrom(info.CallbackInfo.ReturnType)) 81 | { 82 | _asyncCallback = (Func)info.CallbackInfo.CreateDelegate(AsyncCallbackType, node); 83 | _callbackType = CallbackTypes.Async; 84 | } 85 | if (_callbackType == CallbackTypes.None) Debug.LogWarning($"FlowPort Callback for '{node}.{info.CallbackInfo.Name}' did not have one of the following method signatures [Action, Func, Func]"); 86 | } 87 | 88 | public IFlowPort Clone(IGraph graph) 89 | { 90 | string newName = (Node is IGraphFlowPortNode graphPortNode) ? graphPortNode.Name : $"On {Name}"; 91 | var port = new FlowPort 92 | { 93 | Id = new PortId(graph.NodeId, newName), 94 | Node = graph, 95 | Name = newName, 96 | Direction = Direction.Flip(), 97 | Capacity = Capacity, 98 | Flow = Flow, 99 | }; 100 | 101 | switch (Direction) 102 | { 103 | case PortDirection.Input: 104 | Linked = port; 105 | break; 106 | case PortDirection.Output: 107 | port.Linked = this; 108 | break; 109 | } 110 | 111 | return port; 112 | } 113 | 114 | public override void Initialize(ref IFlow flow) 115 | { 116 | Flow = flow; 117 | } 118 | 119 | public virtual IEnumerable Execute() 120 | { 121 | switch (Direction) 122 | { 123 | case PortDirection.Input: 124 | switch (_callbackType) 125 | { 126 | case CallbackTypes.Simple: 127 | _simpleCallback?.Invoke(Flow); 128 | break; 129 | case CallbackTypes.Sync: 130 | yield return _syncCallback(Flow); 131 | break; 132 | case CallbackTypes.Async: 133 | foreach (var item in _asyncCallback(Flow)) 134 | { 135 | yield return item; 136 | } 137 | break; 138 | } 139 | 140 | if (Linked != null) 141 | { 142 | //Debug.Log($"Linked Port '{Node} {this}' => '{Linked.Node} {Linked}'"); 143 | yield return Linked; 144 | } 145 | break; 146 | case PortDirection.Output: 147 | switch (_callbackType) 148 | { 149 | // TODO: Validate FlowOut ports shouldn't support the other callbacks 150 | case CallbackTypes.Simple: 151 | _simpleCallback?.Invoke(Flow); 152 | break; 153 | } 154 | foreach (var input in Graph.FlowOutConnections.SafeGet(Id)) 155 | { 156 | var nextNode = Graph.GetNode(input.Node); 157 | var nextPort = nextNode?.FlowInPorts[input.Port]; 158 | if (nextNode != null && nextPort != null) 159 | { 160 | // Debug.Log($"Traversing Towards Port {nextPort}"); 161 | yield return nextPort; 162 | } 163 | } 164 | break; 165 | } 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /Engine/Port/FlowPort.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1441a3f2de0dd95448a43f47dc5a36eb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Port/FlowPortAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | public interface IFlowPortAttribute : IPortAttribute 7 | { 8 | string Callback { get; } 9 | MethodInfo CallbackInfo { get; } 10 | void SetCallbackInfo(MethodInfo callbackInfo); 11 | } 12 | 13 | public abstract class FlowPortAttribute : PortAttribute, IFlowPortAttribute 14 | { 15 | public string Callback { get; set; } = string.Empty; 16 | 17 | public MethodInfo CallbackInfo { get; private set; } = null; 18 | 19 | public void SetCallbackInfo(MethodInfo callbackInfo) 20 | { 21 | CallbackInfo = callbackInfo; 22 | } 23 | } 24 | 25 | [AttributeUsage(AttributeTargets.Field)] 26 | public sealed class FlowInAttribute : FlowPortAttribute 27 | { 28 | public override PortDirection Direction => PortDirection.Input; 29 | 30 | public override PortCapacity Capacity { get; set; } = PortCapacity.Multi; 31 | 32 | public FlowInAttribute() {} 33 | public FlowInAttribute(string callback = null) 34 | { 35 | Callback = callback; 36 | } 37 | 38 | 39 | } 40 | 41 | [AttributeUsage(AttributeTargets.Field)] 42 | public sealed class FlowOutAttribute : FlowPortAttribute 43 | { 44 | public override PortDirection Direction => PortDirection.Output; 45 | 46 | public override PortCapacity Capacity { get; set; } = PortCapacity.Multi; 47 | 48 | public FlowOutAttribute() {} 49 | public FlowOutAttribute(string callback = null) 50 | { 51 | Callback = callback; 52 | } 53 | } 54 | 55 | public static class FlowAttributeExtensions 56 | { 57 | public static IFlowPort GetOrCreatePort(this IFlowPortAttribute self, INode node) 58 | { 59 | var port = (IFlowPort)((IPortAttribute)self).GetOrCreatePort(node); 60 | port.Definition(node, self); 61 | return port; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Engine/Port/FlowPortAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 242008faa7bd497ebbe0ea2784c328bc 3 | timeCreated: 1607317266 -------------------------------------------------------------------------------- /Engine/Port/Port.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RedOwl.Sleipnir.Engine 4 | { 5 | public interface IPort 6 | { 7 | PortId Id { get; set; } 8 | 9 | IGraph Graph { get; } 10 | INode Node { get; } 11 | 12 | string Name { get; } 13 | 14 | PortDirection Direction { get; } 15 | 16 | PortCapacity Capacity { get; } 17 | bool GraphPort { get; } 18 | 19 | Type ValueType { get; } 20 | 21 | IFlow Flow { get; } 22 | 23 | void Initialize(ref IFlow flow); 24 | } 25 | 26 | public abstract class Port : IPort 27 | { 28 | public PortId Id { get; set; } 29 | public IGraph Graph => Node.Graph; 30 | public INode Node { get; internal set; } 31 | public string Name { get; internal set; } 32 | public PortDirection Direction { get; protected set; } 33 | public PortCapacity Capacity { get; protected set; } 34 | public bool GraphPort { get; protected set; } 35 | public Type ValueType { get; protected set; } 36 | public IFlow Flow { get; protected set; } 37 | public abstract void Initialize(ref IFlow flow); 38 | 39 | public override string ToString() => $"{Node}.{Name}({Direction}|{Capacity})"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Engine/Port/Port.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 683df36bc993f364cadb3e242e8e9eab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Port/PortAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UnityEngine.Scripting; 4 | 5 | namespace RedOwl.Sleipnir.Engine 6 | { 7 | public interface IPortAttribute 8 | { 9 | FieldInfo Info { get; } 10 | string Name { get; } 11 | PortDirection Direction { get; } 12 | PortCapacity Capacity { get; } 13 | bool GraphPort { get; } 14 | void SetInfo(FieldInfo info); 15 | } 16 | 17 | public abstract class PortAttribute : PreserveAttribute, IPortAttribute 18 | { 19 | public FieldInfo Info { get; private set; } 20 | public string Name { get; set; } = null; 21 | public abstract PortDirection Direction { get; } 22 | public abstract PortCapacity Capacity { get; set; } 23 | public bool GraphPort { get; set; } = false; 24 | 25 | public void SetInfo(FieldInfo info) 26 | { 27 | Info = info; 28 | Name = Name ?? info.Name; 29 | } 30 | } 31 | 32 | public static class PortAttributeExtensions 33 | { 34 | public static IPort GetOrCreatePort(this IPortAttribute self, INode node) 35 | { 36 | var port = (IPort)self.Info.GetValue(node); 37 | if (port == null) 38 | { 39 | port = (IPort)Activator.CreateInstance(self.Info.FieldType); 40 | self.Info.SetValue(node, port); 41 | } 42 | return port; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Engine/Port/PortAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79c341e3d3b14ce1956379f104868036 3 | timeCreated: 1610108876 -------------------------------------------------------------------------------- /Engine/Port/PortCapacity.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public enum PortCapacity 4 | { 5 | Single, 6 | Multi, 7 | } 8 | } -------------------------------------------------------------------------------- /Engine/Port/PortCapacity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 702291179db44a6e8a3815a50dc1e96e 3 | timeCreated: 1610116704 -------------------------------------------------------------------------------- /Engine/Port/PortDirection.cs: -------------------------------------------------------------------------------- 1 | namespace RedOwl.Sleipnir.Engine 2 | { 3 | public enum PortDirection 4 | { 5 | Input, 6 | Output, 7 | } 8 | 9 | public static class PortDirectionExtensions 10 | { 11 | public static PortDirection Flip(this PortDirection self) 12 | { 13 | return self == PortDirection.Input ? PortDirection.Output : PortDirection.Input; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Engine/Port/PortDirection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d31ce58f798d42aca49e579a8bf9f70d 3 | timeCreated: 1610108633 -------------------------------------------------------------------------------- /Engine/Port/PortId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | [Serializable] 7 | public struct PortId 8 | { 9 | [field: SerializeField] 10 | public string Node { get; private set; } 11 | 12 | [field: SerializeField] 13 | public string Port { get; private set; } 14 | 15 | public PortId(string node, string port) 16 | { 17 | Node = node; 18 | Port = port; 19 | } 20 | 21 | public override string ToString() => $"{Node}.{Port}"; 22 | } 23 | } -------------------------------------------------------------------------------- /Engine/Port/PortId.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f4c0c49e66a34c82a3f612f6888f9933 3 | timeCreated: 1610116721 -------------------------------------------------------------------------------- /Engine/Port/ValuePort.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | using UnityEngine.Scripting; 5 | 6 | namespace RedOwl.Sleipnir.Engine 7 | { 8 | public interface IValuePort : IPort 9 | { 10 | object DefaultValue { get; } 11 | object WeakValue { get; } 12 | T GetValue(); 13 | void Definition(INode node, IValuePortAttribute info); 14 | IValuePort Clone(IGraph graph); 15 | void MarkDirty(); 16 | IEnumerable Execute(); 17 | } 18 | 19 | [Preserve] 20 | public class ValuePort : Port, IValuePort 21 | { 22 | private static readonly Type VoidType = typeof(void); 23 | private static readonly Type SimpleCallbackType = typeof(Action); 24 | private static readonly Type ValueCallbackType = typeof(Func); 25 | 26 | public enum CallbackTypes 27 | { 28 | None, 29 | Simple, 30 | Value, 31 | } 32 | 33 | private IValuePort Linked { get; set; } 34 | 35 | private CallbackTypes _callbackType; 36 | private Action _simpleCallback; 37 | private Func _valueCallback; 38 | 39 | 40 | public object DefaultValue { get; private set; } 41 | public object WeakValue => Value; 42 | 43 | 44 | // TODO: Should we have ValuePorts actually store a value internally rather then reaching out to the flow? 45 | public T Value 46 | { 47 | get 48 | { 49 | switch (Direction) 50 | { 51 | case PortDirection.Input: 52 | var connections = Graph.ValueInConnections.SafeGet(Id); 53 | if (connections.Count == 0) return GetValue(); 54 | foreach (var connection in connections) 55 | { 56 | var nextNode = Graph.GetNode(connection.Node); // TODO: Needs saftey check? 57 | var nextPort = nextNode?.ValueOutPorts?[connection.Port]; // TODO: Needs saftey check? 58 | if (nextNode != null && nextPort != null) 59 | { 60 | return nextPort.GetValue(); 61 | } 62 | } 63 | 64 | break; 65 | case PortDirection.Output: 66 | return GetValue(); 67 | } 68 | 69 | return Flow != null && Flow.ContainsKey(Id) ? Flow.Get(Id) : (T) DefaultValue; 70 | } 71 | set 72 | { 73 | if (Flow == null) 74 | { 75 | DefaultValue = value; 76 | } 77 | else 78 | { 79 | Flow.Set(Id, value); 80 | } 81 | } 82 | } 83 | 84 | public bool IsConnected 85 | { 86 | get 87 | { 88 | switch (Direction) 89 | { 90 | case PortDirection.Input: 91 | return Graph.ValueInConnections.SafeGet(Id).Count > 0; 92 | case PortDirection.Output: 93 | // TODO: Broken / Needs To be Fixed 94 | return Graph.ValueInConnections.SafeGet(Id).Count > 0; 95 | } 96 | 97 | return false; 98 | } 99 | } 100 | 101 | public TValue GetValue() 102 | { 103 | switch (_callbackType) 104 | { 105 | case CallbackTypes.Simple: 106 | _simpleCallback?.Invoke(); 107 | // WHAT DO WE RETURN? 108 | break; 109 | case CallbackTypes.Value: 110 | // TODO: is there a better way? 111 | return (TValue)(object) _valueCallback(); 112 | } 113 | 114 | return (TValue) DefaultValue; 115 | } 116 | 117 | [Preserve] 118 | public ValuePort() 119 | { 120 | Value = default; 121 | } 122 | 123 | public ValuePort(T defaultValue) 124 | { 125 | Value = defaultValue; 126 | } 127 | 128 | public void SetDefault(T defaultValue) => DefaultValue = defaultValue; 129 | 130 | public void Definition(INode node, IValuePortAttribute info) 131 | { 132 | Id = new PortId(node.NodeId, info.Name); 133 | Node = node; 134 | Name = info.Name; 135 | Direction = info.Direction; 136 | Capacity = info.Capacity; 137 | GraphPort = info.GraphPort; 138 | ValueType = typeof(T); 139 | if (info.CallbackInfo == null) return; 140 | var parameters = info.CallbackInfo.GetParameters(); 141 | if (parameters.Length > 0) 142 | { 143 | Debug.LogWarning($"ValuePort Callback for '{node}.{info.CallbackInfo.Name}' has {parameters.Length} parameter(s). Callback cannot accept any parameters"); 144 | return; 145 | } 146 | if (VoidType.IsAssignableFrom(info.CallbackInfo.ReturnType)) 147 | { 148 | _simpleCallback = (Action)info.CallbackInfo.CreateDelegate(SimpleCallbackType, node); 149 | _callbackType = CallbackTypes.Simple; 150 | } 151 | if (ValueType.IsAssignableFrom(info.CallbackInfo.ReturnType)) 152 | { 153 | _valueCallback = (Func)info.CallbackInfo.CreateDelegate(ValueCallbackType, node); 154 | _callbackType = CallbackTypes.Value; 155 | } 156 | if (_callbackType == CallbackTypes.None) Debug.LogWarning($"ValuePort Callback for '{node}.{info.CallbackInfo.Name}' did not have one of the following method signatures [Action, Func]"); 157 | } 158 | 159 | public IValuePort Clone(IGraph graph) 160 | { 161 | string newName = (Node is IGraphValuePortNode graphPortNode) ? graphPortNode.Name : $"{Name} {Direction.Flip()}"; 162 | var port = new ValuePort() 163 | { 164 | Id = new PortId(graph.NodeId, newName), 165 | Node = graph, 166 | Name = newName, 167 | Direction = Direction.Flip(), 168 | Capacity = Capacity, 169 | ValueType = ValueType, 170 | DefaultValue = DefaultValue, 171 | Flow = Flow, 172 | Linked = this, 173 | }; 174 | return port; 175 | } 176 | 177 | public override void Initialize(ref IFlow flow) 178 | { 179 | Flow = flow; 180 | Flow.Set(Id, (T)DefaultValue); 181 | } 182 | 183 | public void MarkDirty() 184 | { 185 | switch (Direction) 186 | { 187 | case PortDirection.Input: 188 | Debug.Log("Fix This"); 189 | break; 190 | case PortDirection.Output: 191 | // TODO: It is very tough to walk this direction in Value Ports - need to fix this 192 | foreach (var connection in Graph.ValueInConnections) 193 | { 194 | foreach (var connected in connection.Value) 195 | { 196 | if (connected.Node == Id.Node && connected.Port == Id.Port) Graph.GetNode(connection.Key.Node)?.MarkDirty(); 197 | } 198 | } 199 | break; 200 | } 201 | } 202 | 203 | public IEnumerable Execute() 204 | { 205 | // TODO: Refactor This - There has to be a better way 206 | switch (Direction) 207 | { 208 | case PortDirection.Input: 209 | foreach (var connection in Graph.ValueInConnections.SafeGet(Id)) 210 | { 211 | var nextNode = Graph.GetNode(connection.Node); // TODO: Needs saftey check? 212 | var nextPort = nextNode?.ValueOutPorts[connection.Port]; // TODO: Needs saftey check? 213 | if (nextNode != null && nextPort != null) 214 | { 215 | yield return nextPort; 216 | Flow.Set(Id, nextPort.WeakValue); 217 | } 218 | } 219 | if (Linked != null) 220 | { 221 | // Debug.Log($"[Input] Has Linked Port | {this} => {Linked}"); 222 | Flow.Set(Linked.Id, Value); 223 | } 224 | break; 225 | case PortDirection.Output: 226 | if (Linked != null) 227 | { 228 | foreach (var connection in Linked.Graph.ValueInConnections.SafeGet(Linked.Id)) 229 | { 230 | var nextNode = Linked.Graph.GetNode(connection.Node); // TODO: Needs saftey check? 231 | var nextPort = nextNode?.ValueOutPorts[connection.Port]; // TODO: Needs saftey check? 232 | if (nextNode != null && nextPort != null) 233 | { 234 | yield return nextPort; 235 | Flow.Set(Id, nextPort.WeakValue); 236 | } 237 | } 238 | } 239 | else 240 | { 241 | Flow.Set(Id, Value); 242 | } 243 | break; 244 | } 245 | } 246 | 247 | public static implicit operator T(ValuePort self) => self.Value; 248 | public static implicit operator ValuePort(T self) => new ValuePort(self); 249 | } 250 | } -------------------------------------------------------------------------------- /Engine/Port/ValuePort.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a78ff8db3d2dac047abcbf25d3b60276 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Engine/Port/ValuePortAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RedOwl.Sleipnir.Engine 5 | { 6 | public interface IValuePortAttribute : IPortAttribute 7 | { 8 | string Callback { get; } 9 | MethodInfo CallbackInfo { get; } 10 | void SetCallbackInfo(MethodInfo callbackInfo); 11 | } 12 | 13 | public abstract class ValuePortAttribute : PortAttribute, IValuePortAttribute 14 | { 15 | public string Callback { get; set; } = string.Empty; 16 | 17 | public MethodInfo CallbackInfo { get; private set; } = null; 18 | 19 | public void SetCallbackInfo(MethodInfo callbackInfo) 20 | { 21 | CallbackInfo = callbackInfo; 22 | } 23 | } 24 | 25 | [AttributeUsage(AttributeTargets.Field)] 26 | public sealed class ValueInAttribute : ValuePortAttribute 27 | { 28 | public override PortDirection Direction => PortDirection.Input; 29 | 30 | public override PortCapacity Capacity { get; set; } = PortCapacity.Single; 31 | 32 | public ValueInAttribute() {} 33 | public ValueInAttribute(string callback = null) 34 | { 35 | Callback = callback; 36 | } 37 | } 38 | 39 | [AttributeUsage(AttributeTargets.Field)] 40 | public sealed class ValueOutAttribute : ValuePortAttribute 41 | { 42 | public override PortDirection Direction => PortDirection.Output; 43 | 44 | public override PortCapacity Capacity { get; set; } = PortCapacity.Multi; 45 | 46 | public ValueOutAttribute() {} 47 | public ValueOutAttribute(string callback = null) 48 | { 49 | Callback = callback; 50 | } 51 | } 52 | 53 | public static class ValueAttributeExtensions 54 | { 55 | public static IValuePort GetOrCreatePort(this IValuePortAttribute self, INode node) 56 | { 57 | var port = (IValuePort)((IPortAttribute)self).GetOrCreatePort(node); 58 | port.Definition(node, self); 59 | return port; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Engine/Port/ValuePortAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b67a6ea311e542f1aa4e0989fb508c94 3 | timeCreated: 1606796420 -------------------------------------------------------------------------------- /Engine/RedOwl.Sleipnir.Engine.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RedOwl.Sleipnir.Engine", 3 | "references": [ 4 | "Unity.Mathematics" 5 | ], 6 | "includePlatforms": [], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Engine/RedOwl.Sleipnir.Engine.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01515ad9c5ad5604b839035bd76f1747 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 24 | 25 |
26 | Built with ❤︎ by 27 | Red Owl Games 28 |
29 | 30 |

31 | If this library helps you out consider 32 | buying me a coffee!Buy me a coffee 33 |

34 | 35 | ---- 36 | 37 | ## Why 38 | 39 | After surveying the landscape of graph frameworks for Unity for the past few years 40 | and trying to use them I found myself always painting myself into corners, having to work 41 | in constrained ways and/or just not liking the API setup around building graph based tools. 42 | 43 | I've also come to love Odin Inspector and the power it brings to writing editor tools. 44 | 45 | So here we are - this is our love letter to Odin by marrying it with a graph framework that 46 | gets out of your way and hopefully makes it trivial to build new graph based tools ontop. 47 | 48 | ## Features 49 | 50 | - Strong interface based design - Graphs and Nodes can be POCO's (Plain Old CLR Object - IE Classes) 51 | - Allows for c# code to generate graph data programmatically 52 | - Built ontop of Unity 53 | - Uses Unity's Serializer to reduce outside dependencies (leverages the new `SerializeReference` features) 54 | - Uses UI toolkit (UIElements) to support the future of Editor UI's 55 | - Natively supports Odin Inspector inside the Graph Editor Window 56 | - Complete control over flow/execution - Allows for writing any kind of graph, statemachine, behaviour, flow, etc 57 | - Batteries Included - Base classes for Graph, Node, Port and Flow to get up and working quickly 58 | - Also includes standard nodes and graph types that could be shared from graph tool to graph tool 59 | 60 | ## Quickstart 61 | 62 | To write a new custom node quickly you inherit from the base Node class and define your ports. 63 | 64 | ```csharp 65 | using RedOwl.Sleipnir.Engine; 66 | 67 | [Node] 68 | public class AddNode : Node 69 | { 70 | [FlowIn(nameof(OnEnter))] public FlowPort Enter; 71 | [FlowOut] public FlowPort Exit; 72 | 73 | [ValueIn] public ValuePort Left; 74 | [ValueIn] public ValuePort Right; 75 | 76 | [ValueOut] public ValuePort Output; 77 | 78 | protected IFlowPort OnEnter(IFlow flow) 79 | { 80 | Output.Value = Left.Value + Right.Value; 81 | return Exit; 82 | } 83 | } 84 | ``` 85 | 86 | The above is a simple definition of a node that performs an "add" math operation on the 87 | "left" and "right" value in ports and pushs the result to the value out port. 88 | 89 | To then make use of this new node you navigate to your Project Browser in Unity right click 90 | and then `Create` -> `Red Owl` -> `Graph` 91 | 92 | This creates a scriptable object asset in your project that can serialize graph data using 93 | unity's serializer. From there you double click the asset to open the Graph Editor window. 94 | 95 | From there you can press spacebar and a node search window will open - navigate to your new 96 | node and that will place an instance in your graph. You should beable to create a graph like the following image. 97 | 98 | 99 | 100 | The follow graph will perform an addition on the 2 float values and log the result to the console. 101 | 102 | Cheers! (For more examples dig into the codebase at [./Engine/Nodes](https://github.com/red-owl-games/Sleipnir/tree/master/Engine/Nodes)) 103 | 104 | ## Creating Graphs via C# 105 | 106 | ```csharp 107 | private static void CreateSampleGraph() 108 | { 109 | var graph = new Graph(); 110 | 111 | var startNode = graph.Add(new StartNode {NodePosition = new Vector2(128, 0)}); 112 | var logNode1 = graph.Add(new LogNode {NodePosition = new Vector2(0, 100)}); 113 | var logNode2 = graph.Add(new LogNode {NodePosition = new Vector2(200, 100)}); 114 | var floatNode1 = graph.Add(new FloatValueNode(10) {NodePosition = new Vector2(-200, 0)}); 115 | var floatNode2 = graph.Add(new FloatValueNode(100) {NodePosition = new Vector2(-200, 200)}); 116 | var floatNode3 = graph.Add(new FloatValueNode(5) {NodePosition = new Vector2(-200, 400)}); 117 | 118 | // Flow 119 | graph.Connect(startNode.Start, logNode1.Enter); 120 | graph.Connect(startNode.Start, logNode2.Enter); 121 | 122 | // Values 123 | graph.Connect(floatNode1.Value, logNode1.Message); 124 | graph.Connect(floatNode2.Value, logNode2.Message); 125 | 126 | GraphAsset.Save(graph, "Generated", "Resources/Graphs"); 127 | } 128 | ``` 129 | 130 | ## Installing 131 | 132 | In *Package Manager* click *Add package from git URL* and use the following: 133 | 134 | ``` 135 | https://github.com/red-owl-games/Sleipnir.git 136 | ``` 137 | 138 | ### Things to checkout 139 | 140 | https://github.com/buunguyen/fasterflect 141 | http://introspectingcode.blogspot.com/2011/06/dynamically-compile-code-at-runtime.html -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c06ff4b5c03cf5648a30b9f39ed1cfda 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4af9e0964d140f64eacf50137544606c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/SleipnirWindow.uss: -------------------------------------------------------------------------------- 1 | GridBackground#Grid { 2 | --line-color: rgba(150, 150, 150, 0.05); 3 | --thick-line-color: rgba(10, 10, 10, 0.7); 4 | --spacing: 20; 5 | } 6 | #contents #collapsible-area #extension { 7 | background-color: rgba(46, 46, 46, 0.8); 8 | } 9 | 10 | #OdinTree { 11 | margin-top: 1px; 12 | margin-bottom: 1px; 13 | padding: 1px 0px 0px 0px; 14 | background-color: rgba(63, 63, 63, 0.8); 15 | } 16 | 17 | #FlowPorts { 18 | flex-direction: row; 19 | justify-content: space-between; 20 | } 21 | 22 | #FlowPorts > .typeControlPort { 23 | --port-color: rgba(200, 117, 48, 1); 24 | } 25 | 26 | #FlowPorts.FlowInPorts > Port { 27 | /*flex-direction: column;*/ 28 | } 29 | 30 | #FlowPorts.FlowInPorts > .typeControlPort > #connector { 31 | border-radius: 10px 10px 0 0; 32 | } 33 | 34 | #FlowPorts.FlowOutPorts > Port { 35 | /* TODO: To make this work i think i'll need fix the port connector manipulator 36 | flex-direction: row; 37 | */ 38 | } 39 | 40 | #FlowPorts.FlowOutPorts > .typeControlPort > #connector { 41 | border-radius: 0 0 10px 10px; 42 | } 43 | -------------------------------------------------------------------------------- /Resources/SleipnirWindow.uss.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3766421d7d64f7640bb9ebbe1557aa11 3 | ScriptedImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 2 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} 11 | disableValidation: 0 12 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-owl-games/Sleipnir/ef7bd4cce40c66820fe832a98154abe0af16b14d/example.png -------------------------------------------------------------------------------- /example.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: be50ee3b3dfa80445bcd6b2aeb95f608 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: -1 36 | aniso: -1 37 | mipBias: -100 38 | wrapU: -1 39 | wrapV: -1 40 | wrapW: -1 41 | nPOTScale: 1 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 0 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 0 54 | spriteTessellationDetail: -1 55 | textureType: 0 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.redowl.sleipnir", 3 | "version": "1.0.0", 4 | "displayName": "RedOwl - Sleipnir", 5 | "description": "A unity graph editor framework for unity that works with odin inspector", 6 | "unity": "2019.4", 7 | "keywords": [ 8 | "graph", 9 | "editor", 10 | "framework" 11 | ], 12 | "author": { 13 | "name": "Red Owl Games", 14 | "email": "support@redowlgames.com", 15 | "url": "https://redowlgames.com" 16 | } 17 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bda9a37238718fa44a0a06e820b28225 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------