using System; using Gamecraft.Wires; using Svelto.DataStructures; using Svelto.ECS; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Blocks.Engines { /// /// Engine which executes signal actions /// 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; } = "TechbloxModdingAPISignalGameEngine"; 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 Wire, EGID ID) CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort) { EGID wireEGID = new EGID(BuildModeWiresGroups.NewWireEntityId, BuildModeWiresGroups.WiresGroup.Group); EntityInitializer wireInitializer = Factory.BuildEntity(wireEGID); wireInitializer.Init(new WireEntityStruct { sourceBlockEGID = startBlock, sourcePortUsage = startPort, destinationBlockEGID = endBlock, destinationPortUsage = endPort }); return (wireInitializer.Get(), wireEGID); } public ref WireEntityStruct GetWire(EGID wire) { if (!entitiesDB.Exists(wire)) { throw new WiringException($"Wire {wire} does not exist"); } return ref entitiesDB.QueryEntity(wire); } public ref PortEntityStruct GetPort(EGID port) { if (!entitiesDB.Exists(port)) { throw new WiringException($"Port {port} does not exist (yet?)"); } return ref entitiesDB.QueryEntity(port); } public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input) { ExclusiveGroup group = input ? NamedExclusiveGroup.Group : NamedExclusiveGroup.Group; uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber; EGID egid = new EGID(id, group); if (!entitiesDB.Exists(egid)) { throw new WiringException("Port does not exist"); } return ref entitiesDB.QueryEntity(egid); } public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input) { var bps = entitiesDB.QueryEntityOptional(block); if (!bps) { throw new BlockException("Block does not exist"); } return ref GetPortByOffset(bps, portNumber, input); } public ref T GetComponent(EGID egid) where T : unmanaged, IEntityComponent { return ref entitiesDB.QueryEntity(egid); } public bool Exists(EGID egid) where T : struct, IEntityComponent { return entitiesDB.Exists(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, count) = GetSignalStruct(signalID, out uint index, input); if (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, count) = GetSignalStruct(signalID, out uint index, input); if (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, count) = GetSignalStruct(signalID, out uint index, input); return count > 0 ? array[index].valueAsFloat : 0f; } public uint[] GetSignalIDs(EGID blockID, bool input = true) { ref BlockPortsStruct bps = ref entitiesDB.QueryEntity(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(blockID); EGID[] inputs = new EGID[ports.inputCount]; for (uint i = 0; i < ports.inputCount; i++) { inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup.Group); } return inputs; } public EGID[] GetSignalOutputs(EGID blockID) { BlockPortsStruct ports = entitiesDB.QueryEntity(blockID); EGID[] outputs = new EGID[ports.outputCount]; for (uint i = 0; i < ports.outputCount; i++) { outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup.Group); } return outputs; } public OptionalRef MatchBlockIOToPort(Block block, byte portUsage, bool output) { return MatchBlockIOToPort(block.Id, portUsage, output); } public OptionalRef MatchBlockIOToPort(EGID block, byte portUsage, bool output) { if (!entitiesDB.Exists(block)) return default; var group = output ? NamedExclusiveGroup.Group : NamedExclusiveGroup.Group; BlockPortsStruct ports = entitiesDB.QueryEntity(block); if (!entitiesDB.TryQueryMappedEntities(group, out var mapper)) return default; for (uint i = 0; i < (output ? ports.outputCount : ports.inputCount); ++i) { uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i; if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) || array[index].usage != portUsage) continue; return new OptionalRef(array, index, new EGID(entityID, group)); } return default; } public OptionalRef MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID) { var (wires, ids, count) = entitiesDB.QueryEntities(NamedExclusiveGroup.Group); for (uint i = 0; i < count; i++) { if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID) || (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID)) { wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group); return new OptionalRef(wires, i); } } wireID = default; return default; } public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, 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(startBlock); startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup.Group) }; } EGID[] endPorts; if (startPort == byte.MaxValue) { // search all input ports on destination block endPorts = GetSignalInputs(endBlock); } else { BlockPortsStruct ports = entitiesDB.QueryEntity(endBlock); endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup.Group) }; } for (int endIndex = 0; endIndex < endPorts.Length; endIndex++) { PortEntityStruct endPES = entitiesDB.QueryEntity(endPorts[endIndex]); for (int startIndex = 0; startIndex < startPorts.Length; startIndex++) { PortEntityStruct startPES = entitiesDB.QueryEntity(startPorts[startIndex]); foreach (var wireOpt in entitiesDB.QueryEntitiesOptional( NamedExclusiveGroup.Group)) { var wire = wireOpt.Get(); if ((wire.destinationPortUsage == endPES.usage && wire.destinationBlockEGID == endBlock) && (wire.sourcePortUsage == startPES.usage && wire.sourceBlockEGID == startBlock)) { return wireOpt.EGID; } } } } return default; } public OptionalRef GetChannelDataStruct(EGID portID) { var port = GetPort(portID); var (channels, count) = entitiesDB.QueryEntities(NamedExclusiveGroup.Group); return port.firstChannelIndexCachedInSim < count ? new OptionalRef(channels, port.firstChannelIndexCachedInSim) : default; } public EGID[] GetElectricBlocks() { var res = new FasterList(); foreach (var ((coll, ids, count), _) in entitiesDB.QueryEntities()) { for (int i = 0; i < count; i++) { ref BlockPortsStruct s = ref coll[i]; //res.Add(s.ID); - TODO: Would need to search for the groups for each block } } return res.ToArray(); } public EGID[] WiredToInput(EGID block, byte port) { return entitiesDB .QueryEntitiesOptional(NamedExclusiveGroup.Group) .ToArray(wire => wire.ID, wire => wire.Component.destinationPortUsage == port && wire.Component.destinationBlockEGID == block); } public EGID[] WiredToOutput(EGID block, byte port) { return entitiesDB .QueryEntitiesOptional(NamedExclusiveGroup.Group) .ToArray(wire => wire.ID, wire => wire.Component.sourcePortUsage == port && wire.Component.sourceBlockEGID == block); } private EntityCollection GetSignalStruct(uint signalID, out uint index, bool input = true) { ExclusiveGroup group = input ? NamedExclusiveGroup.Group : NamedExclusiveGroup.Group; if (entitiesDB.Exists(signalID, group)) { index = entitiesDB.QueryEntity(signalID, group).firstChannelIndexCachedInSim; var channelData = entitiesDB.QueryEntities(NamedExclusiveGroup.Group); return channelData; } index = 0; return default; //count: 0 } } }