Add wiring API and improve signal support

This commit is contained in:
NGnius (Graham) 2020-08-03 12:45:38 -04:00
parent ca0e6e089d
commit 708dbdd81d
13 changed files with 706 additions and 18 deletions

View file

@ -122,6 +122,7 @@ namespace GamecraftModdingAPI
new Dictionary<Type, ExclusiveGroupStruct[]> new Dictionary<Type, ExclusiveGroupStruct[]>
{ {
{typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}}, {typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}},
{typeof(LogicGate), new [] {CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP}},
{typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}}, {typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}},
{typeof(MusicBlock), new[] {CommonExclusiveGroups.BUILD_MUSIC_BLOCK_GROUP}}, {typeof(MusicBlock), new[] {CommonExclusiveGroups.BUILD_MUSIC_BLOCK_GROUP}},
{typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}}, {typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}},
@ -420,6 +421,8 @@ namespace GamecraftModdingAPI
GameEngineManager.AddGameEngine(BlockEngine); GameEngineManager.AddGameEngine(BlockEngine);
GameEngineManager.AddGameEngine(BlockEventsEngine); GameEngineManager.AddGameEngine(BlockEventsEngine);
GameEngineManager.AddGameEngine(ScalingEngine); GameEngineManager.AddGameEngine(ScalingEngine);
GameEngineManager.AddGameEngine(SignalEngine);
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
} }
/// <summary> /// <summary>

View file

@ -40,4 +40,26 @@ namespace GamecraftModdingAPI.Blocks
{ {
} }
} }
public class WiringException : BlockException
{
public WiringException()
{
}
public WiringException(string message) : base(message)
{
}
}
public class WireInvalidException : WiringException
{
public WireInvalidException()
{
}
public WireInvalidException(string message) : base(message)
{
}
}
} }

View file

@ -4,6 +4,7 @@ using Gamecraft.Wires;
using GamecraftModdingAPI; using GamecraftModdingAPI;
using GamecraftModdingAPI.Tests; using GamecraftModdingAPI.Tests;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks namespace GamecraftModdingAPI.Blocks
{ {
@ -100,6 +101,24 @@ namespace GamecraftModdingAPI.Blocks
//Assert.Log(b.Track.ToString()); //Assert.Log(b.Track.ToString());
if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return; if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return;
} }
[APITestCase(TestType.EditMode)]
public static void TestLogicGate()
{
Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1);
LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue.");
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return;
if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return;
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
//if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return;
LogicGate target = null;
if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return;
Wire newWire = null;
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return;
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return;
}
} }
#endif #endif
} }

View file

@ -0,0 +1,16 @@
using RobocraftX.Common;
using Svelto.ECS;
namespace GamecraftModdingAPI.Blocks
{
public class LogicGate : SignalingBlock
{
public LogicGate(EGID id) : base(id)
{
}
public LogicGate(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP))
{
}
}
}

View file

@ -1,4 +1,5 @@
using Svelto.ECS; using System;
using Svelto.ECS;
using Svelto.DataStructures; using Svelto.DataStructures;
using Gamecraft.Wires; using Gamecraft.Wires;
@ -9,7 +10,7 @@ namespace GamecraftModdingAPI.Blocks
/// <summary> /// <summary>
/// Engine which executes signal actions /// Engine which executes signal actions
/// </summary> /// </summary>
public class SignalEngine : IApiEngine public class SignalEngine : IApiEngine, IFactoryEngine
{ {
public const float POSITIVE_HIGH = 1.0f; public const float POSITIVE_HIGH = 1.0f;
public const float NEGATIVE_HIGH = -1.0f; public const float NEGATIVE_HIGH = -1.0f;
@ -20,6 +21,8 @@ namespace GamecraftModdingAPI.Blocks
public EntitiesDB entitiesDB { set; private get; } public EntitiesDB entitiesDB { set; private get; }
public IEntityFactory Factory { get; set; }
public bool isRemovable => false; public bool isRemovable => false;
public bool IsInGame = false; public bool IsInGame = false;
@ -34,7 +37,73 @@ namespace GamecraftModdingAPI.Blocks
IsInGame = true; IsInGame = true;
} }
// implementations for Signal static class // implementations for block wiring
public EGID 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,
});
return wireEGID;
}
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 : struct, 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) public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true)
{ {
@ -123,7 +192,7 @@ namespace GamecraftModdingAPI.Blocks
return inputs; return inputs;
} }
public EGID[] GetSignalOutputs(EGID blockID) public EGID[] GetSignalOutputs(EGID blockID)
{ {
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID); BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
EGID[] outputs = new EGID[ports.outputCount]; EGID[] outputs = new EGID[ports.outputCount];
@ -134,6 +203,18 @@ namespace GamecraftModdingAPI.Blocks
return outputs; 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 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 ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists)
{ {
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
@ -152,6 +233,57 @@ namespace GamecraftModdingAPI.Blocks
return ref defRef[0]; 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) public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists)
{ {
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
@ -175,6 +307,69 @@ namespace GamecraftModdingAPI.Blocks
return res.ToArray(); 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 : struct, 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) private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true)
{ {
ExclusiveGroup group = input ExclusiveGroup group = input

View file

@ -16,18 +16,10 @@ namespace GamecraftModdingAPI.Blocks
{ {
public SignalingBlock(EGID id) : base(id) public SignalingBlock(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public SignalingBlock(uint id) : base(id) public SignalingBlock(uint id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
/// <summary> /// <summary>
@ -85,5 +77,91 @@ namespace GamecraftModdingAPI.Blocks
{ {
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount); get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount);
} }
/// <summary>
/// Connect an output on this block to an input on another block.
/// </summary>
/// <param name="sourcePort">Output port number.</param>
/// <param name="destination">Input block.</param>
/// <param name="destinationPort">Input port number.</param>
/// <returns>The wire connection</returns>
/// <exception cref="WiringException">The wire could not be created.</exception>
public Wire Connect(byte sourcePort, SignalingBlock destination, byte destinationPort)
{
if (sourcePort >= OutputCount)
{
throw new WiringException("Source port does not exist");
}
if (destinationPort >= destination.InputCount)
{
throw new WiringException("Destination port does not exist");
}
return Wire.Connect(this, sourcePort, destination, destinationPort);
}
/// <summary>
/// The port's name.
/// This is localized to the user's language, so this is not reliable for port identification.
/// </summary>
/// <param name="port">Port number.</param>
/// <param name="input">Whether the port is an input (true) or an output (false).</param>
/// <returns>The localized port name.</returns>
public string PortName(byte port, bool input)
{
BlockPortsStruct bps = BlockEngine.GetBlockInfo(this, (BlockPortsStruct a) => a);
PortEntityStruct pes = SignalEngine.GetPortByOffset(this, port, input);
return pes.portNameLocalised;
}
/// <summary>
/// The input port's name.
/// </summary>
/// <param name="port">Input port number.</param>
/// <returns>The port name, localized to the user's language.</returns>
public string InputPortName(byte port) => PortName(port, true);
/// <summary>
/// The output port's name.
/// </summary>
/// <param name="port">Output port number.</param>
/// <returns>The port name, localized to the user's language.</returns>
public string OutputPortName(byte port) => PortName(port, false);
/// <summary>
/// All wires connected to the input port.
/// These wires will always be wired output -> input.
/// </summary>
/// <param name="port">Port number.</param>
/// <returns>Wires connected to the input port.</returns>
public Wire[] ConnectedToInput(byte port)
{
if (port >= InputCount) throw new WiringException($"Port input {port} does not exist");
EGID[] wireEgids = SignalEngine.WiredToInput(Id, port);
Wire[] wires = new Wire[wireEgids.Length];
for (uint i = 0; i < wireEgids.Length; i++)
{
wires[i] = new Wire(wireEgids[i]);
}
return wires;
}
/// <summary>
/// All wires connected to the output port.
/// These wires will always be wired output -> input.
/// </summary>
/// <param name="port">Port number.</param>
/// <returns>Wires connected to the output port.</returns>
public Wire[] ConnectedToOutput(byte port)
{
if (port >= OutputCount) throw new WiringException($"Port output {port} does not exist");
EGID[] wireEgids = SignalEngine.WiredToOutput(Id, port);
Wire[] wires = new Wire[wireEgids.Length];
for (uint i = 0; i < wireEgids.Length; i++)
{
wires[i] = new Wire(wireEgids[i]);
}
return wires;
}
} }
} }

View file

@ -0,0 +1,338 @@
using System;
using Gamecraft.Wires;
using Svelto.ECS;
using Svelto.ECS.Experimental;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
public class Wire
{
internal static SignalEngine signalEngine;
protected EGID startPortEGID;
protected EGID endPortEGID;
protected EGID startBlockEGID;
protected EGID endBlockEGID;
protected EGID wireEGID;
protected bool inputToOutput;
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
{
EGID wireEgid = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
return new Wire(start, end, startPort, endPort, wireEgid, false);
}
/// <summary>
/// An existing wire connection ending at the specified input.
/// If multiple exist, this will return the first one found.
/// </summary>
/// <param name="end">Destination block.</param>
/// <param name="endPort">Port number.</param>
/// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns>
public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort)
{
EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
if (!exists) return null;
WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists);
if (exists)
{
return new Wire(new Block(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort);
}
return null;
}
/// <summary>
/// An existing wire connection starting at the specified output.
/// If multiple exist, this will return the first one found.
/// </summary>
/// <param name="start">Source block entity ID.</param>
/// <param name="startPort">Port number.</param>
/// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns>
public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort)
{
EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (!exists) return null;
WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists);
if (exists)
{
return new Wire(start, new Block(wire.destinationBlockEGID), startPort, wire.destinationPortUsage);
}
return null;
}
/// <summary>
/// Construct a wire object from an existing connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number, or guess if omitted.</param>
/// <param name="endPort">Ending port number, or guess if omitted.</param>
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception>
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue)
{
startBlockEGID = start.Id;
endBlockEGID = end.Id;
// find block ports
WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort);
if (exists)
{
wireEGID = wire.ID;
endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
inputToOutput = false;
}
else
{
// flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort);
if (exists)
{
wireEGID = wire.ID;
endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
inputToOutput = true; // end is actually the source
// NB: start and end are handled exactly as they're received as params.
// This makes wire traversal easier, but makes logic in this class a bit more complex
}
else
{
throw new WireInvalidException("Wire not found");
}
}
}
/// <summary>
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number.</param>
/// <param name="endPort">Ending port number.</param>
/// <param name="wire">The wire ID.</param>
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param>
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
{
this.startBlockEGID = start.Id;
this.endBlockEGID = end.Id;
this.inputToOutput = inputToOutput;
this.wireEGID = wire;
if (inputToOutput)
{
endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
}
else
{
endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
}
}
/// <summary>
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid)
{
this.wireEGID = wireEgid;
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
this.startBlockEGID = wire.sourceBlockEGID;
this.endBlockEGID = wire.destinationBlockEGID;
this.inputToOutput = false;
}
/// <summary>
/// The wire's in-game id.
/// </summary>
public EGID Id
{
get => wireEGID;
}
/// <summary>
/// The wire's signal value, as a float.
/// </summary>
public float Float
{
get
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return 0f;
return cds.valueAsFloat;
}
set
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsFloat = value;
}
}
/// <summary>
/// The wire's string signal.
/// </summary>
public string String
{
get
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return "";
return cds.valueAsEcsString;
}
set
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsEcsString.Set(value);
}
}
/// <summary>
/// The wire's raw string signal.
/// </summary>
public ECSString ECSString
{
get
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return default;
return cds.valueAsEcsString;
}
set
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsEcsString = value;
}
}
/// <summary>
/// The wire's signal id.
/// I'm 50% sure this is useless.
/// </summary>
public uint SignalId
{
get
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return uint.MaxValue;
return cds.valueAsID;
}
set
{
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsID = value;
}
}
/// <summary>
/// The block at the beginning of the wire.
/// </summary>
public SignalingBlock Start
{
get => new SignalingBlock(startBlockEGID);
}
/// <summary>
/// The port number that the beginning of the wire connects to.
/// </summary>
public byte StartPort
{
get
{
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
if (inputToOutput)
{
return wire.destinationPortUsage;
}
return wire.sourcePortUsage;
}
}
/// <summary>
/// The block at the end of the wire.
/// </summary>
public SignalingBlock End
{
get => new SignalingBlock(endBlockEGID);
}
/// <summary>
/// The port number that the end of the wire connects to.
/// </summary>
public byte EndPort
{
get
{
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
if (inputToOutput)
{
return wire.sourcePortUsage;
}
return wire.destinationPortUsage;
}
}
/// <summary>
/// Create a copy of the wire object where the direction of the wire is guaranteed to be from a block output to a block input.
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
/// </summary>
/// <returns>A copy of the wire object.</returns>
public Wire OutputToInputCopy()
{
return new Wire(wireEGID);
}
/// <summary>
/// Convert the wire object to the direction the signal flows.
/// Signals on wires always flows from a block output port to a block input port.
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
/// </summary>
public void OutputToInputInPlace()
{
if (inputToOutput)
{
inputToOutput = false;
// swap inputs and outputs
EGID temp = endBlockEGID;
endBlockEGID = startBlockEGID;
startBlockEGID = temp;
temp = endPortEGID;
endPortEGID = startPortEGID;
startPortEGID = temp;
}
}
public override string ToString()
{
if (signalEngine.Exists<WireEntityStruct>(wireEGID))
{
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {Start.PortName(StartPort, inputToOutput)}) -> ({End.Type}::{EndPort} aka {End.PortName(EndPort, !inputToOutput)})";
}
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(Id)}: {End.Id}, ({Start.Type}::{StartPort}) -> ({End.Type}::{EndPort})";
}
internal static void Init() { }
}
}

View file

@ -14,7 +14,7 @@ namespace GamecraftModdingAPI.Events
/// Keeps track of event handlers and emitters. /// Keeps track of event handlers and emitters.
/// This is used to add, remove and get API event handlers and emitters. /// This is used to add, remove and get API event handlers and emitters.
/// </summary> /// </summary>
[Obsolete] [Obsolete("This will be removed in an upcoming update. Use the new C# event architecture from GamecraftModdingAPI.App")]
public static class EventManager public static class EventManager
{ {
private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>(); private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>();

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>1.4.0</Version> <Version>1.5.0</Version>
<Authors>Exmods</Authors> <Authors>Exmods</Authors>
<PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression> <PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl> <PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl>

View file

@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Reflection; using System.Reflection;
using GamecraftModdingAPI.Blocks;
using HarmonyLib; using HarmonyLib;
using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Utility;
@ -49,6 +49,7 @@ namespace GamecraftModdingAPI
harmony.PatchAll(currentAssembly); harmony.PatchAll(currentAssembly);
// init utility // init utility
Logging.MetaDebugLog($"Initializing Utility"); Logging.MetaDebugLog($"Initializing Utility");
#pragma warning disable 0612,0618
Utility.GameState.Init(); Utility.GameState.Init();
Utility.VersionTracking.Init(); Utility.VersionTracking.Init();
// create default event emitters // create default event emitters
@ -61,6 +62,7 @@ namespace GamecraftModdingAPI
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameSwitchedTo, "GamecraftModdingAPIGameSwitchedToEventEmitter", false)); EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameSwitchedTo, "GamecraftModdingAPIGameSwitchedToEventEmitter", false));
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.buildEngine); EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.buildEngine);
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine); EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine);
#pragma warning restore 0612,0618
// init block implementors // init block implementors
Logging.MetaDebugLog($"Initializing Blocks"); Logging.MetaDebugLog($"Initializing Blocks");
// init inventory // init inventory
@ -70,6 +72,7 @@ namespace GamecraftModdingAPI
// init object-oriented classes // init object-oriented classes
Player.Init(); Player.Init();
Block.Init(); Block.Init();
Wire.Init();
GameClient.Init(); GameClient.Init();
AsyncUtils.Init(); AsyncUtils.Init();
GamecraftModdingAPI.App.Client.Init(); GamecraftModdingAPI.App.Client.Init();

View file

@ -68,6 +68,7 @@ namespace GamecraftModdingAPI.Tests
//Utility.VersionTracking.Enable();//(very) unstable //Utility.VersionTracking.Enable();//(very) unstable
// debug/test handlers // debug/test handlers
#pragma warning disable 0612
HandlerBuilder.Builder() HandlerBuilder.Builder()
.Name("appinit API debug") .Name("appinit API debug")
.Handle(EventType.ApplicationInitialized) .Handle(EventType.ApplicationInitialized)
@ -115,7 +116,7 @@ namespace GamecraftModdingAPI.Tests
.Handle(EventType.Menu) .Handle(EventType.Menu)
.OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); }) .OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); })
.Build(); .Build();
#pragma warning restore 0612
/*HandlerBuilder.Builder("enter game from menu test") /*HandlerBuilder.Builder("enter game from menu test")
.Handle(EventType.Menu) .Handle(EventType.Menu)
.OnActivation(() => .OnActivation(() =>
@ -254,6 +255,19 @@ namespace GamecraftModdingAPI.Tests
}) })
.Build(); .Build();
CommandBuilder.Builder()
.Name("WireTest")
.Description("Place two blocks and then wire them together")
.Action(() =>
{
LogicGate notBlock = Block.PlaceNew<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0));
LogicGate andBlock = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, new float3(2, 2, 0));
// connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position)
Wire conn = notBlock.Connect(0, andBlock, 1);
Logging.CommandLog(conn.ToString());
})
.Build();
GameClient.SetDebugInfo("InstalledMods", InstalledMods); GameClient.SetDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) => Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);

View file

@ -209,7 +209,7 @@ namespace GamecraftModdingAPI.Tests
{ {
Assert.Fail($"Build test '{m}' raised an exception: {e.ToString()}"); Assert.Fail($"Build test '{m}' raised an exception: {e.ToString()}");
} }
yield return Yield.It; yield return Yield.It;
} }
} }
} }

View file

@ -38,7 +38,7 @@ PROJECT_NAME = "GamecraftModdingAPI"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "v1.3.0" PROJECT_NUMBER = "v1.5.0"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a