using System; using System.Reflection; using UnityEngine; namespace Misaki.GraphView { [Serializable] public abstract class BaseNode : SlotContainer { [SerializeField] private GraphObject _graphObject; [SerializeField] private string _id = Guid.NewGuid().ToString(); private bool _isExecuted; public Rect position; public GraphObject GraphObject => _graphObject; public string Id => _id; /// /// Initialize the node with the graph object, this method is called when the node is added to the graph. /// public virtual void Initialize(GraphObject graph) { _graphObject = graph; var type = GetType(); var fields = type.GetFields(ConstResource.NODE_FIELD_BINDING_FLAGS); var inputSlotIndex = 0; var outputSlotIndex = 0; foreach (var field in fields) { var inputAttribute = field.GetCustomAttribute(); if (inputAttribute != null) { var inputSlot = new Slot(this, new SlotData { slotName = field.Name, nodeID = Id, slotIndex = inputSlotIndex++, direction = SlotDirection.Input, valueType = field.FieldType.FullName }); AddInput(inputSlot); continue; } var outputAttribute = field.GetCustomAttribute(); if (outputAttribute != null) { var outputSlot = new Slot(this, new SlotData { slotName = field.Name, nodeID = Id, slotIndex = outputSlotIndex++, direction = SlotDirection.Output, valueType = field.FieldType.FullName }); AddOutput(outputSlot); } } } /// /// Unload the node from the graph, this method is called when the node is removed from the graph. /// public virtual void UnLoad() { } /// /// Get the slot by the index and direction. /// /// /// /// public Slot GetSlot(int index, SlotDirection direction) { return direction switch { SlotDirection.Input => Inputs[index], SlotDirection.Output => Outputs[index], _ => null }; } /// /// Unlink all the slots of the node. /// public void UnlinkAllSlots() { foreach (var input in Inputs) { input.UnlinkAll(); _graphObject.RemoveAllConnectionsForSlot(input); } foreach (var output in Outputs) { output.UnlinkAll(); _graphObject.RemoveAllConnectionsForSlot(output); } } /// /// Execute the node. /// public void Execute() { if (_isExecuted) { return; } PullData(); OnExecute(); PushData(); _isExecuted = true; } private void PullData() { foreach (var input in Inputs) { var property = GetType().GetField(input.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS); if (property == null) continue; OnPullData(input); if (input.LinkedSlotData.Count == 0) { continue; } property.SetValue(this, input.value); } } protected virtual void OnPullData(Slot input) { } private void PushData() { foreach (var output in Outputs) { var property = GetType().GetField(output.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS); if (property == null) continue; OnPushData(output); output.value = property.GetValue(this); foreach (var slotData in output.LinkedSlotData) { var slot = _graphObject.GetNode(slotData.nodeID).GetSlot(slotData.slotIndex, slotData.direction); if (slotData.valueType == output.slotData.valueType || output.slotData.valueType == typeof(object).FullName) { slot.ReceiveData(output.value); } else if (_graphObject.ValueConverterManager != null && _graphObject.ValueConverterManager.TryConvert(output.slotData.GetValueType(), slotData.GetValueType(), output.value, out var data)) { slot.ReceiveData(data); } } } } protected virtual void OnPushData(Slot output) { } public bool IsExecuted() { return _isExecuted; } public void ClearExecuteFlag() { _isExecuted = false; } /// /// The execution logic of the node. /// protected abstract void OnExecute(); } }