First commit
This commit is contained in:
3
Runtime/Models/Graph.meta
Normal file
3
Runtime/Models/Graph.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7850cecc27694a8d9233cd518f358e15
|
||||
timeCreated: 1730120175
|
||||
113
Runtime/Models/Graph/GraphObject.cs
Normal file
113
Runtime/Models/Graph/GraphObject.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
public abstract class GraphObject : ScriptableObject
|
||||
{
|
||||
[SerializeReference]
|
||||
private List<BaseNode> _nodes = new();
|
||||
[SerializeField]
|
||||
private List<SlotConnection> _connections = new();
|
||||
[SerializeReference]
|
||||
private List<ExposedProperty> _exposedProperties = new();
|
||||
|
||||
private readonly Dictionary<string, BaseNode> _nodeMap = new();
|
||||
|
||||
public ReadOnlyCollection<BaseNode> Nodes => _nodes.AsReadOnly();
|
||||
public ReadOnlyCollection<SlotConnection> Connections => _connections.AsReadOnly();
|
||||
public ReadOnlyCollection<ExposedProperty> ExposedProperties => _exposedProperties.AsReadOnly();
|
||||
|
||||
public Vector3 graphPosition;
|
||||
public Vector3 graphScale = Vector3.one;
|
||||
|
||||
public virtual IValueConverterManager ValueConverterManager { get; } = null;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_nodeMap.Clear();
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
TryAddNodeToMap(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNode(BaseNode baseNode)
|
||||
{
|
||||
_nodes.Add(baseNode);
|
||||
TryAddNodeToMap(baseNode);
|
||||
baseNode.Initialize(this);
|
||||
}
|
||||
|
||||
public void RemoveNode(BaseNode baseNode)
|
||||
{
|
||||
_nodes.Remove(baseNode);
|
||||
RemoveNodeFromMap(baseNode);
|
||||
baseNode.UnLoad();
|
||||
baseNode.UnlinkAllSlots();
|
||||
}
|
||||
|
||||
public bool TryAddNodeToMap(BaseNode baseNode)
|
||||
{
|
||||
return _nodeMap.TryAdd(baseNode.Id, baseNode);
|
||||
}
|
||||
|
||||
public void RemoveNodeFromMap(BaseNode baseNode)
|
||||
{
|
||||
_nodeMap.Remove(baseNode.Id);
|
||||
}
|
||||
|
||||
public BaseNode GetNode(string id)
|
||||
{
|
||||
return _nodeMap.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public bool TryGetNode(string id, out BaseNode baseNode)
|
||||
{
|
||||
return _nodeMap.TryGetValue(id, out baseNode);
|
||||
}
|
||||
|
||||
public void AddConnection(SlotConnection connection)
|
||||
{
|
||||
_connections.Add(connection);
|
||||
}
|
||||
|
||||
public void RemoveConnection(SlotConnection connection)
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
}
|
||||
|
||||
public void RemoveAllConnectionsForSlot(Slot slot)
|
||||
{
|
||||
_connections.RemoveAll(connection =>
|
||||
connection.InputSlotData == slot.slotData || connection.OutputSlotData == slot.slotData);
|
||||
}
|
||||
|
||||
public SlotConnection TryGetConnection(Slot input, Slot output)
|
||||
{
|
||||
return _connections.FirstOrDefault(connection =>
|
||||
connection.InputSlotData == input.slotData && connection.OutputSlotData == output.slotData);
|
||||
}
|
||||
|
||||
public void AddExposedProperty(ExposedProperty property)
|
||||
{
|
||||
_exposedProperties.Add(property);
|
||||
}
|
||||
|
||||
public void RemoveExposedProperty(ExposedProperty property)
|
||||
{
|
||||
_exposedProperties.Remove(property);
|
||||
}
|
||||
|
||||
public void SetTransform(ITransform transform)
|
||||
{
|
||||
graphPosition = transform.position;
|
||||
graphScale = transform.scale;
|
||||
}
|
||||
|
||||
public abstract void Execute();
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/Graph/GraphObject.cs.meta
Normal file
3
Runtime/Models/Graph/GraphObject.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fcee63f4bdd40319ff8bf4ee3c62735
|
||||
timeCreated: 1730120188
|
||||
9
Runtime/Models/GraphModel.cs
Normal file
9
Runtime/Models/GraphModel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
public class GraphModel : ScriptableObject
|
||||
{
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/GraphModel.cs.meta
Normal file
3
Runtime/Models/GraphModel.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a3cc049d1ed445c99ef6a55b49c0020
|
||||
timeCreated: 1730279834
|
||||
8
Runtime/Models/Nodes.meta
Normal file
8
Runtime/Models/Nodes.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f9989d323a3eb0498bd69ce937c4744
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
194
Runtime/Models/Nodes/BaseNode.cs
Normal file
194
Runtime/Models/Nodes/BaseNode.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the node with the graph object, this method is called when the node is added to the graph.
|
||||
/// </summary>
|
||||
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<NodeInputAttribute>();
|
||||
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<NodeOutputAttribute>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unload the node from the graph, this method is called when the node is removed from the graph.
|
||||
/// </summary>
|
||||
public virtual void UnLoad()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the slot by the index and direction.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="direction"></param>
|
||||
/// <returns></returns>
|
||||
public Slot GetSlot(int index, SlotDirection direction)
|
||||
{
|
||||
return direction switch
|
||||
{
|
||||
SlotDirection.Input => Inputs[index],
|
||||
SlotDirection.Output => Outputs[index],
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlink all the slots of the node.
|
||||
/// </summary>
|
||||
public void UnlinkAllSlots()
|
||||
{
|
||||
foreach (var input in Inputs)
|
||||
{
|
||||
input.UnlinkAll();
|
||||
_graphObject.RemoveAllConnectionsForSlot(input);
|
||||
}
|
||||
|
||||
foreach (var output in Outputs)
|
||||
{
|
||||
output.UnlinkAll();
|
||||
_graphObject.RemoveAllConnectionsForSlot(output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the node.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The execution logic of the node.
|
||||
/// </summary>
|
||||
protected abstract void OnExecute();
|
||||
}
|
||||
}
|
||||
2
Runtime/Models/Nodes/BaseNode.cs.meta
Normal file
2
Runtime/Models/Nodes/BaseNode.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 670887239e46bea498021b59a16eef15
|
||||
26
Runtime/Models/Nodes/PropertyInputNode.cs
Normal file
26
Runtime/Models/Nodes/PropertyInputNode.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
public class PropertyInputNode : BaseNode
|
||||
{
|
||||
[SerializeReference]
|
||||
private ExposedProperty _property;
|
||||
|
||||
public ExposedProperty Property => _property;
|
||||
|
||||
[NodeOutput]
|
||||
public object value;
|
||||
|
||||
public PropertyInputNode(ExposedProperty property)
|
||||
{
|
||||
_property = property;
|
||||
}
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
value = _property.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/Nodes/PropertyInputNode.cs.meta
Normal file
3
Runtime/Models/Nodes/PropertyInputNode.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18828dc1acb341d382ed33d371b71225
|
||||
timeCreated: 1730511664
|
||||
40
Runtime/Models/Nodes/SlotContainer.cs
Normal file
40
Runtime/Models/Nodes/SlotContainer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class SlotContainer
|
||||
{
|
||||
[SerializeField]
|
||||
private List<Slot> _inputs = new ();
|
||||
[SerializeField]
|
||||
private List<Slot> _outputs = new ();
|
||||
|
||||
public ReadOnlyCollection<Slot> Inputs => _inputs.AsReadOnly();
|
||||
public ReadOnlyCollection<Slot> Outputs => _outputs.AsReadOnly();
|
||||
|
||||
public void AddInput(Slot input)
|
||||
{
|
||||
_inputs.Add(input);
|
||||
}
|
||||
|
||||
public void AddOutput(Slot output)
|
||||
{
|
||||
_outputs.Add(output);
|
||||
}
|
||||
|
||||
public void ClearInputs()
|
||||
{
|
||||
_inputs.Clear();
|
||||
}
|
||||
|
||||
public void ClearOutputs()
|
||||
{
|
||||
_outputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/Nodes/SlotContainer.cs.meta
Normal file
3
Runtime/Models/Nodes/SlotContainer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a8f9433f38644d78ab2a8bc47cc54bd
|
||||
timeCreated: 1730116658
|
||||
3
Runtime/Models/Properties.meta
Normal file
3
Runtime/Models/Properties.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15655a58d5d949209d5dd3cb504b3907
|
||||
timeCreated: 1730122800
|
||||
43
Runtime/Models/Properties/ExposedProperty.cs
Normal file
43
Runtime/Models/Properties/ExposedProperty.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
[Serializable]
|
||||
public class ExposedProperty : IEquatable<ExposedProperty>
|
||||
{
|
||||
public string id = Guid.NewGuid().ToString();
|
||||
public string propertyName;
|
||||
public string propertyType;
|
||||
|
||||
public virtual object Value { get; set; }
|
||||
public virtual Type GetValueType() => Value == null ? typeof(object) : Value.GetType();
|
||||
|
||||
public bool Equals(ExposedProperty other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return id == other.id && propertyName == other.propertyName && propertyType == other.propertyType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((ExposedProperty)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(id, propertyName, propertyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/Properties/ExposedProperty.cs.meta
Normal file
3
Runtime/Models/Properties/ExposedProperty.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8072bd56f5fc4873b2db86cf44ed6c57
|
||||
timeCreated: 1730454701
|
||||
8
Runtime/Models/Slots.meta
Normal file
8
Runtime/Models/Slots.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa6d50cc997d5e04d8709064239d1b22
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
101
Runtime/Models/Slots/Slot.cs
Normal file
101
Runtime/Models/Slots/Slot.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
[Serializable]
|
||||
public struct SlotData : IEquatable<SlotData>
|
||||
{
|
||||
public string slotName;
|
||||
public string nodeID;
|
||||
public int slotIndex;
|
||||
public SlotDirection direction;
|
||||
public string valueType;
|
||||
|
||||
public bool Equals(SlotData other)
|
||||
{
|
||||
return slotName == other.slotName && nodeID == other.nodeID && slotIndex == other.slotIndex && direction == other.direction && valueType == other.valueType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SlotData other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(slotName, nodeID, slotIndex, direction, valueType);
|
||||
}
|
||||
|
||||
public static bool operator ==(SlotData left, SlotData right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(SlotData left, SlotData right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Slot
|
||||
{
|
||||
[SerializeField]
|
||||
private List<SlotData> _linkedSlotData = new();
|
||||
|
||||
public ReadOnlyCollection<SlotData> LinkedSlotData => _linkedSlotData.AsReadOnly();
|
||||
|
||||
[SerializeReference]
|
||||
public BaseNode owner;
|
||||
public SlotData slotData;
|
||||
|
||||
public object value;
|
||||
|
||||
public Slot(BaseNode owner, SlotData slotData)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.slotData = slotData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link the current slot with another slot.
|
||||
/// </summary>
|
||||
/// <param name="other"> The slot need to be linked </param>
|
||||
public void Link(Slot other)
|
||||
{
|
||||
if (other.slotData.direction == slotData.direction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_linkedSlotData.Contains(other.slotData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_linkedSlotData.Add(other.slotData);
|
||||
other._linkedSlotData.Add(slotData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlink the current slot with another slot.
|
||||
/// </summary>
|
||||
/// <param name="other"> The slot need to be unlinked </param>
|
||||
public void Unlink(Slot other)
|
||||
{
|
||||
_linkedSlotData.Remove(other.slotData);
|
||||
other._linkedSlotData.Remove(slotData);
|
||||
}
|
||||
|
||||
public void ReceiveData(object data)
|
||||
{
|
||||
value = data;
|
||||
// We move this to PullData method in BaseNode
|
||||
//owner.GetType().GetField(slotData.slotName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(owner, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/Models/Slots/Slot.cs.meta
Normal file
2
Runtime/Models/Slots/Slot.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a905458a4a2f3848b7f29f182fad7f9
|
||||
47
Runtime/Models/Slots/SlotConnection.cs
Normal file
47
Runtime/Models/Slots/SlotConnection.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Misaki.GraphView
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a connection between two connection ports.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SlotConnection : IEquatable<SlotConnection>
|
||||
{
|
||||
[SerializeField]
|
||||
private SlotData _inputSlotData;
|
||||
|
||||
[SerializeField]
|
||||
private SlotData _outputSlotData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SlotConnection" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="inputSlotData">The input connection port.</param>
|
||||
/// <param name="outputSlotData">The output connection port.</param>
|
||||
public SlotConnection(SlotData inputSlotData, SlotData outputSlotData)
|
||||
{
|
||||
_inputSlotData = inputSlotData;
|
||||
_outputSlotData = outputSlotData;
|
||||
}
|
||||
|
||||
public SlotData InputSlotData => _inputSlotData;
|
||||
public SlotData OutputSlotData => _outputSlotData;
|
||||
|
||||
public bool Equals(SlotConnection other)
|
||||
{
|
||||
return _inputSlotData.Equals(other._inputSlotData) && _outputSlotData.Equals(other._outputSlotData);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SlotConnection other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(InputSlotData, OutputSlotData);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Models/Slots/SlotConnection.cs.meta
Normal file
3
Runtime/Models/Slots/SlotConnection.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfede99dc664420b8b64a5ad265db47b
|
||||
timeCreated: 1730127456
|
||||
Reference in New Issue
Block a user