using System; using Svelto.ECS; using Svelto.DataStructures; using Gamecraft.Wires; using GamecraftModdingAPI.Engines; namespace GamecraftModdingAPI.Blocks { /// <summary> /// Engine which executes signal actions /// </summary> public class SignalEngine : IApiEngine, IFactoryEngine { public const float POSITIVE_HIGH = 1.0f; public const float NEGATIVE_HIGH = -1.0f; public const float HIGH = 1.0f; public const float ZERO = 0.0f; public string Name { get; } = "GamecraftModdingAPISignalGameEngine"; public EntitiesDB entitiesDB { set; private get; } public IEntityFactory Factory { get; set; } public bool isRemovable => false; public bool IsInGame = false; public void Dispose() { IsInGame = false; } public void Ready() { IsInGame = true; } // implementations for block wiring public WireEntityStruct CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort) { EGID wireEGID = new EGID(WiresExclusiveGroups.NewWireEntityId, NamedExclusiveGroup<WiresGroup>.Group); EntityComponentInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID); wireInitializer.Init(new WireEntityStruct { sourceBlockEGID = startBlock, sourcePortUsage = startPort, destinationBlockEGID = endBlock, destinationPortUsage = endPort, ID = wireEGID }); return wireInitializer.Get<WireEntityStruct>(); } public ref WireEntityStruct GetWire(EGID wire) { if (!entitiesDB.Exists<WireEntityStruct>(wire)) { throw new WiringException($"Wire {wire} does not exist"); } return ref entitiesDB.QueryEntity<WireEntityStruct>(wire); } public ref PortEntityStruct GetPort(EGID port) { if (!entitiesDB.Exists<PortEntityStruct>(port)) { throw new WiringException($"Port {port} does not exist (yet?)"); } return ref entitiesDB.QueryEntity<PortEntityStruct>(port); } public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input) { ExclusiveGroup group = input ? NamedExclusiveGroup<InputPortsGroup>.Group : NamedExclusiveGroup<OutputPortsGroup>.Group; uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber; EGID egid = new EGID(id, group); if (!entitiesDB.Exists<PortEntityStruct>(egid)) { throw new WiringException("Port does not exist"); } return ref entitiesDB.QueryEntity<PortEntityStruct>(egid); } public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input) { BlockPortsStruct bps = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out bool exists); if (!exists) { throw new BlockException("Block does not exist"); } return ref GetPortByOffset(bps, portNumber, input); } public ref T GetComponent<T>(EGID egid) where T : unmanaged, IEntityComponent { return ref entitiesDB.QueryEntity<T>(egid); } public bool Exists<T>(EGID egid) where T : struct, IEntityComponent { return entitiesDB.Exists<T>(egid); } public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true) { signalID = GetSignalIDs(blockID, input)[0]; return SetSignal(signalID, signal); } public bool SetSignal(uint signalID, float signal, bool input = true) { var array = GetSignalStruct(signalID, out uint index, input); if (array.count > 0) array[index].valueAsFloat = signal; return false; } public float AddSignal(EGID blockID, float signal, out uint signalID, bool clamp = true, bool input = true) { signalID = GetSignalIDs(blockID, input)[0]; return AddSignal(signalID, signal, clamp, input); } public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true) { var array = GetSignalStruct(signalID, out uint index, input); if (array.count > 0) { ref var channelData = ref array[index]; channelData.valueAsFloat += signal; if (clamp) { if (channelData.valueAsFloat > POSITIVE_HIGH) { channelData.valueAsFloat = POSITIVE_HIGH; } else if (channelData.valueAsFloat < NEGATIVE_HIGH) { channelData.valueAsFloat = NEGATIVE_HIGH; } return channelData.valueAsFloat; } } return signal; } public float GetSignal(EGID blockID, out uint signalID, bool input = true) { signalID = GetSignalIDs(blockID, input)[0]; return GetSignal(signalID, input); } public float GetSignal(uint signalID, bool input = true) { var array = GetSignalStruct(signalID, out uint index, input); return array.count > 0 ? array[index].valueAsFloat : 0f; } public uint[] GetSignalIDs(EGID blockID, bool input = true) { ref BlockPortsStruct bps = ref entitiesDB.QueryEntity<BlockPortsStruct>(blockID); uint[] signals; if (input) { signals = new uint[bps.inputCount]; for (uint i = 0u; i < bps.inputCount; i++) { signals[i] = bps.firstInputID + i; } } else { signals = new uint[bps.outputCount]; for (uint i = 0u; i < bps.outputCount; i++) { signals[i] = bps.firstOutputID + i; } } return signals; } public EGID[] GetSignalInputs(EGID blockID) { BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID); EGID[] inputs = new EGID[ports.inputCount]; for (uint i = 0; i < ports.inputCount; i++) { inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup<InputPortsGroup>.Group); } return inputs; } public EGID[] GetSignalOutputs(EGID blockID) { BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID); EGID[] outputs = new EGID[ports.outputCount]; for (uint i = 0; i < ports.outputCount; i++) { outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup<OutputPortsGroup>.Group); } return outputs; } public EGID MatchBlockInputToPort(Block block, byte portUsage, out bool exists) { BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists); return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group); } public EGID MatchBlockInputToPort(EGID block, byte portUsage, out bool exists) { if (!entitiesDB.Exists<BlockPortsStruct>(block)) { exists = false; return default; } exists = true; BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group); } public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists) { BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists); return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group); } public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists) { if (!entitiesDB.Exists<BlockPortsStruct>(block)) { exists = false; return default; } exists = true; BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group); } public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) { ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); var wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group); for (uint i = 0; i < wires.count; i++) { if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID) || (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID)) { exists = true; return ref wires[i]; } } exists = false; WireEntityStruct[] defRef = new WireEntityStruct[1]; return ref defRef[0]; } public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue) { EGID[] startPorts; if (startPort == byte.MaxValue) { // search all output ports on source block startPorts = GetSignalOutputs(startBlock); } else { BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock); startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<OutputPortsGroup>.Group) }; } EGID[] endPorts; if (startPort == byte.MaxValue) { // search all input ports on destination block endPorts = GetSignalInputs(endBlock); } else { BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock); endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<InputPortsGroup>.Group) }; } EntityCollection<WireEntityStruct> wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group); for (int endIndex = 0; endIndex < endPorts.Length; endIndex++) { PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]); for (int startIndex = 0; startIndex < startPorts.Length; startIndex++) { PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]); for (int w = 0; w < wires.count; w++) { if ((wires[w].destinationPortUsage == endPES.usage && wires[w].destinationBlockEGID == endBlock) && (wires[w].sourcePortUsage == startPES.usage && wires[w].sourceBlockEGID == startBlock)) { exists = true; return ref wires[w]; } } } } exists = false; WireEntityStruct[] defRef = new WireEntityStruct[1]; return ref defRef[0]; } public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists) { ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); var channels = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group); if (port.firstChannelIndexCachedInSim < channels.count) { exists = true; return ref channels[port.firstChannelIndexCachedInSim]; } exists = false; ChannelDataStruct[] defRef = new ChannelDataStruct[1]; return ref defRef[0]; } public EGID[] GetElectricBlocks() { var res = new FasterList<EGID>(); foreach (var (coll, _) in entitiesDB.QueryEntities<BlockPortsStruct>()) foreach (ref BlockPortsStruct s in coll) res.Add(s.ID); return res.ToArray(); } public EGID[] WiredToInput(EGID block, byte port) { WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group, (WireEntityStruct wes) => wes.destinationPortUsage == port && wes.destinationBlockEGID == block); EGID[] result = new EGID[wireEntityStructs.Length]; for (uint i = 0; i < wireEntityStructs.Length; i++) { result[i] = wireEntityStructs[i].ID; } return result; } public EGID[] WiredToOutput(EGID block, byte port) { WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group, (WireEntityStruct wes) => wes.sourcePortUsage == port && wes.sourceBlockEGID == block); EGID[] result = new EGID[wireEntityStructs.Length]; for (uint i = 0; i < wireEntityStructs.Length; i++) { result[i] = wireEntityStructs[i].ID; } return result; } private T[] Search<T>(ExclusiveGroup group, Func<T, bool> isMatch) where T : struct, IEntityComponent { FasterList<T> results = new FasterList<T>(); EntityCollection<T> components = entitiesDB.QueryEntities<T>(group); for (uint i = 0; i < components.count; i++) { if (isMatch(components[i])) { results.Add(components[i]); } } return results.ToArray(); } private ref T GetFromDbOrInitData<T>(Block block, EGID id, out bool exists) where T : unmanaged, IEntityComponent { T[] defRef = new T[1]; if (entitiesDB.Exists<T>(id)) { exists = true; return ref entitiesDB.QueryEntity<T>(id); } if (block == null || block.InitData.Group == null) { exists = false; return ref defRef[0]; } EntityComponentInitializer initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); if (initializer.Has<T>()) { exists = true; return ref initializer.Get<T>(); } exists = false; return ref defRef[0]; } private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true) { ExclusiveGroup group = input ? NamedExclusiveGroup<InputPortsGroup>.Group : NamedExclusiveGroup<OutputPortsGroup>.Group; if (entitiesDB.Exists<PortEntityStruct>(signalID, group)) { index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).anyChannelIndex; var channelData = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group); return channelData; } index = 0; return default; //count: 0 } } }