Add mostly reliable signal & channel API support

This commit is contained in:
NGnius (Graham) 2019-12-25 14:25:53 -05:00
parent 15a73ecfab
commit 2df8da1af5
15 changed files with 522 additions and 10 deletions

View file

@ -33,6 +33,8 @@ namespace GamecraftModdingAPI.Blocks
public static ExclusiveGroup SPAWN_POINTS_DISABLED { get { return CommonExclusiveGroups.SPAWN_POINTS_DISABLED_GROUP; } }
public static ExclusiveGroup OUTPUT_SIGNAL_CHANNELS { get { return CommonExclusiveGroups.CHANNEL_OUTPUT_SIGNAL_GROUPS; } }
/// <summary>
/// The ID of the most recently placed block
/// </summary>

View file

@ -81,7 +81,7 @@ namespace GamecraftModdingAPI.Blocks
public bool IsBuildMode()
{
return this.entitiesDB.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Build;
return SimModeUtil.IsBuildMode(this.entitiesDB);
}
}
}

View file

@ -21,7 +21,7 @@ namespace GamecraftModdingAPI.Blocks
/// <param name="id">The block's id</param>
/// <param name="vector">The rotation amount around the x,y,z-planes</param>
/// <returns></returns>
public static bool MoveBlock(uint id, float3 vector)
public static bool RotateBlock(uint id, float3 vector)
{
if (rotationEngine.IsInGame && rotationEngine.IsBuildMode())
{
@ -37,11 +37,11 @@ namespace GamecraftModdingAPI.Blocks
/// <param name="id">The starting block's id</param>
/// <param name="vector">The rotation around the x,y,z-planes</param>
/// <returns></returns>
public static bool MoveRotateBlocks(uint id, float3 vector)
public static bool RotateConnectedBlocks(uint id, float3 vector)
{
if (rotationEngine.IsInGame && rotationEngine.IsBuildMode())
{
rotationEngine.MoveConnectedBlocks(id, vector);
rotationEngine.RotateConnectedBlocks(id, vector);
return true;
}
return false;

View file

@ -73,14 +73,14 @@ namespace GamecraftModdingAPI.Blocks
}
public float3 MoveConnectedBlocks(uint blockID, Vector3 vector)
public float3 RotateConnectedBlocks(uint blockID, Vector3 vector)
{
throw new NotImplementedException();
}
public bool IsBuildMode()
{
return this.entitiesDB.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Build;
return SimModeUtil.IsBuildMode(this.entitiesDB);
}
}
}

View file

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using RobocraftX.UECS;
using Unity.Entities;
using Svelto.Context;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class SignalEngine : IApiEngine
{
public string Name { get; } = "GamecraftModdingAPISignalGameEngine";
public IEntitiesDB entitiesDB { set; private get; }
public bool IsInGame = false;
private Stack<uint> cubesStack = new Stack<uint>();
private bool stackInUse = false;
private Gamecraft.DataStructures.FasterList<uint> cubesList = new Gamecraft.DataStructures.FasterList<uint>();
private bool listInUse = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Signal static class
public bool SetSignal(uint blockID, uint channel, float signal, out EGID clusterID)
{
clusterID = GetClusterEGID(blockID, channel);
return SetSignal(clusterID, signal);
}
public bool SetSignal(EGID clusterID, float signal)
{
if (entitiesDB.Exists<ChannelOutputSignalDataStruct>(clusterID))
{
entitiesDB.QueryEntity<ChannelOutputSignalDataStruct>(clusterID).outputSignal = signal;
return true;
}
return false;
}
public float AddSignal(uint blockID, uint channel, float signal, out EGID clusterID, bool clamp = true)
{
clusterID = GetClusterEGID(blockID, channel);
return AddSignal(clusterID, signal, clamp);
}
public float AddSignal(EGID clusterID, float signal, bool clamp=true)
{
if (entitiesDB.Exists<ChannelOutputSignalDataStruct>(clusterID))
{
ref ChannelOutputSignalDataStruct chanOutSig = ref entitiesDB.QueryEntity<ChannelOutputSignalDataStruct>(clusterID);
chanOutSig.outputSignal += signal;
if (clamp)
{
if (chanOutSig.outputSignal > Signals.POSITIVE_HIGH)
{
chanOutSig.outputSignal = Signals.POSITIVE_HIGH;
}
else if (chanOutSig.outputSignal < Signals.NEGATIVE_HIGH)
{
chanOutSig.outputSignal = Signals.NEGATIVE_HIGH;
}
return chanOutSig.outputSignal;
}
}
return signal;
}
public float GetSignal(uint blockID, uint channel, out EGID clusterID)
{
clusterID = GetClusterEGID(blockID, channel);
return GetSignal(clusterID);
}
public float GetSignal(EGID clusterID)
{
if (entitiesDB.Exists<ChannelOutputSignalDataStruct>(clusterID))
{
return entitiesDB.QueryEntity<ChannelOutputSignalDataStruct>(clusterID).outputSignal;
}
return 0f;
}
public EGID GetClusterEGID(uint blockID, uint channel)
{
uint[] connectedCubeIDs = GetConductivelyConnectedBlocks(blockID);
uint index;
ElectricityEntityStruct[] structs;
Logging.CommandLog($"Found {connectedCubeIDs.Length} connected cubes");
for (int i = 0; i < connectedCubeIDs.Length; i++)
{
if (entitiesDB.TryQueryEntitiesAndIndex(new EGID(connectedCubeIDs[i], CommonExclusiveGroups.OWNED_BLOCKS_GROUP), out index, out structs)
|| entitiesDB.TryQueryEntitiesAndIndex(new EGID(connectedCubeIDs[i], CommonExclusiveGroups.FUNCTIONAL_BLOCK_PART_GROUP), out index, out structs))
{
ref ConductiveClusterID clusterId = ref entitiesDB.QueryEntity<ConductiveClusterIdStruct>(structs[index].ID).clusterId;
uint operatingChannel = entitiesDB.QueryEntity<SignalOperatingChannelStruct>(structs[index].ID).operatingChannel;
Logging.CommandLog($"Channel {operatingChannel} found");
EGID eGID = new EGID(channel, BlockIdentifiers.OUTPUT_SIGNAL_CHANNELS + clusterId.ID);
if (clusterId.initialized && clusterId.isConductive && entitiesDB.Exists<ChannelOutputSignalDataStruct>(eGID))
{
return eGID;
}
}
}
// failsafe; not 100% reliable
foreach (ref ConductiveClusterIdStruct clusterIdStruct in entitiesDB.QueryEntities<ConductiveClusterIdStruct>(CommonExclusiveGroups.FUNCTIONAL_CUBES_IN_BOTH_SIM_AND_BUILD))
{
EGID eGID = new EGID(channel, BlockIdentifiers.OUTPUT_SIGNAL_CHANNELS + clusterIdStruct.clusterId.ID);
if (clusterIdStruct.clusterId.initialized && clusterIdStruct.clusterId.isConductive && entitiesDB.Exists<ChannelOutputSignalDataStruct>(eGID))
{
return eGID;
}
}
return default;
}
private uint[] GetConductivelyConnectedBlocks(uint blockID)
{
if (!(stackInUse || listInUse))
{
stackInUse = true;
listInUse = true;
cubesStack.Clear();
cubesList.FastClear();
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubesStack, cubesList, (in GridConnectionsEntityStruct g) => { return false; });
uint[] res = cubesList.ToArray();
stackInUse = false;
listInUse = false;
return res;
}
Stack<uint> cubeStack = new Stack<uint>();
Gamecraft.DataStructures.FasterList<uint> cubeList = new Gamecraft.DataStructures.FasterList<uint>();
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubesStack, cubesList, (in GridConnectionsEntityStruct g) => { return false; });
return cubeList.ToArray();
}
public bool IsSimulationMode()
{
return SimModeUtil.IsSimulationMode(this.entitiesDB);
}
}
}

View file

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// [EXPERIMENTAL] Common block signal operations
/// </summary>
public static class Signals
{
// Signal constants
public static readonly float HIGH = 1.0f;
public static readonly float POSITIVE_HIGH = HIGH;
public static readonly float NEGATIVE_HIGH = -1.0f;
public static readonly float LOW = 0.0f;
private static SignalEngine signalEngine = new SignalEngine();
/// <summary>
/// Set a channel to a value in the block's conductive block cluster
/// </summary>
/// <param name="id">The block's id</param>
/// <param name="channel">The channel (1 to 99)</param>
/// <param name="signal">The signal value (-1 to 1; not enforced)</param>
public static void SetSignalConnectedBlocks(uint id, uint channel, float signal)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.SetSignal(id, channel, signal, out EGID _);
}
}
/// <summary>
/// Set a conductive cluster channel to a value
/// </summary>
/// <param name="clusterID">The channel cluster's id</param>
/// <param name="signal">The signal value (-1 to 1; not enforced)</param>
public static void SetSignalCluster(EGID clusterID, float signal)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.SetSignal(clusterID, signal);
}
}
/// <summary>
/// Add a value to a channel signal in the block's conductive block cluster
/// </summary>
/// <param name="id">The block's id</param>
/// <param name="channel">The channel (1 to 99)</param>
/// <param name="signal">The signal value to add</param>
/// <param name="clamp">Whether to clamp the resulting signal value between -1 and 1</param>
public static void AddSignalConnectedBlocks(uint id, uint channel, float signal, bool clamp = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.AddSignal(id, channel, signal, out EGID _, clamp);
}
}
/// <summary>
/// Add a value to a conductive cluster channel
/// </summary>
/// <param name="clusterID">The channel cluster's id</param>
/// <param name="signal">The signal value to add</param>
/// <param name="clamp">Whether to clamp the resulting signal value between -1 and 1</param>
public static void AddSignalCluster(EGID clusterID, float signal, bool clamp = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.AddSignal(clusterID, signal, clamp);
}
}
/// <summary>
/// Get a channel's signal value from the block's conductive block cluster
/// </summary>
/// <param name="id">The block's id</param>
/// <param name="channel">The channel (1 to 99)</param>
/// <returns>The signal value</returns>
public static float GetSignalConnectedBlocks(uint id, uint channel)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.GetSignal(id, channel, out EGID _);
}
return 0f;
}
/// <summary>
/// Get a conductive cluster channel's signal value
/// </summary>
/// <param name="clusterID">The channel cluster's id</param>
/// <returns>The signal value</returns>
public static float GetSignalCluster(EGID clusterID)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.GetSignal(clusterID);
}
return 0f;
}
public static EGID GetClusterID(uint id, uint channel)
{
return signalEngine.GetClusterEGID(id, channel);
}
public static void Init()
{
GameEngineManager.AddGameEngine(signalEngine);
}
}
}

View file

@ -60,7 +60,8 @@ namespace GamecraftModdingAPI.Events
/// <param name="removed">The operation to do when the event is destroyed (if applicable)</param>
/// <param name="type">The type of event to handle</param>
/// <param name="name">The name of the engine</param>
public SimpleEventHandlerEngine(Action activated, Action removed, object type, string name)
/// <param name="simple">A useless parameter to use to avoid Python overload resolution errors</param>
public SimpleEventHandlerEngine(Action activated, Action removed, object type, string name, bool simple = true)
: this((IEntitiesDB _) => { activated.Invoke(); }, (IEntitiesDB _) => { removed.Invoke(); }, type, name) { }
/// <summary>

View file

@ -9,6 +9,7 @@ using Harmony;
using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Events;
using GamecraftModdingAPI.Tasks;
namespace GamecraftModdingAPI
{
@ -52,6 +53,7 @@ namespace GamecraftModdingAPI
Logging.MetaDebugLog($"Initializing Blocks");
Blocks.Movement.Init();
Blocks.Rotation.Init();
Blocks.Signals.Init();
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
}
@ -62,8 +64,15 @@ namespace GamecraftModdingAPI
/// </summary>
public static void Shutdown()
{
if (!IsInitialized)
{
Logging.LogWarning("GamecraftModdingAPI.Main.Shutdown() called but API is not initialized!");
return;
}
Scheduler.Dispose();
var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchAll(currentAssembly.GetName().Name);
harmony = null;
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown");
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.Tasks;
namespace GamecraftModdingAPI.Tasks
{
/// <summary>
/// Interface for asynchronous tasks
/// </summary>
public interface ISchedulable
{
/// <summary>
/// Asynchronous task runner
/// </summary>
/// <returns>A yield-ed Svelto.Tasks-compatible object</returns>
IEnumerator<TaskContract> Run();
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
namespace GamecraftModdingAPI.Tasks
{
/// <summary>
/// An asynchronous task to be performed once
/// </summary>
public class Once : ISchedulable
{
private Action task;
private float delay;
public IEnumerator<TaskContract> Run()
{
yield return new WaitForSecondsEnumerator(delay).Continue();
task();
yield return Yield.It;
}
/// <summary>
/// Construct a single-run task
/// </summary>
/// <param name="task">The task to run once</param>
/// <param name="after">The delay (in seconds) before the task is run</param>
public Once(Action task, float after = 0.0f)
{
this.task = task;
this.delay = after;
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
namespace GamecraftModdingAPI.Tasks
{
/// <summary>
/// An asynchronous repeating task
/// </summary>
public class Repeatable : ISchedulable
{
/// <summary>
/// Determines if the task should continue to repeat
/// </summary>
/// <returns>Whether the task should run again (true) or end (false)</returns>
public delegate bool ShouldContinue();
private ShouldContinue shouldContinue;
private Action task;
private float delay;
public IEnumerator<TaskContract> Run()
{
while (shouldContinue())
{
task();
yield return new WaitForSecondsEnumerator(delay).Continue();
}
yield return Yield.It;
}
/// <summary>
/// Construct a repeating task
/// </summary>
/// <param name="task">The task to repeat</param>
/// <param name="shouldContinue">The check to determine if the task should run again</param>
/// <param name="delay">The time to wait between repeats (in seconds)</param>
public Repeatable(Action task, ShouldContinue shouldContinue, float delay = 0.0f)
{
this.task = task;
this.shouldContinue = shouldContinue;
this.delay = delay;
}
/// <summary>
/// Construct a repeating task
/// </summary>
/// <param name="task">The task to repeat</param>
/// <param name="count">The amount of times to repeat</param>
/// <param name="delay">The time to wait between repeats (in seconds)</param>
public Repeatable(Action task, int count, float delay = 0.0f)
{
this.task = task;
this.shouldContinue = () => { return count-- != 0; };
this.delay = delay;
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.Tasks.Lean;
using Svelto.Tasks.ExtraLean;
namespace GamecraftModdingAPI.Tasks
{
public static class Scheduler
{
public static Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunnerUI
{
get
{
return RobocraftX.Schedulers.Lean.UIScheduler;
}
}
public static Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunnerUI
{
get
{
return RobocraftX.Schedulers.ExtraLean.UIScheduler;
}
}
public static readonly Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunner = new Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner("GamecraftModdingAPIExtraLean");
public static readonly Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunner = new Svelto.Tasks.Lean.Unity.UpdateMonoRunner("GamecraftModdingAPILean");
public static void Schedule(ISchedulable toRun, bool extraLean = false)
{
if (extraLean)
{
toRun.Run().RunOn(extraLeanRunner);
}
else
{
toRun.Run().RunOn(leanRunner);
}
}
public static void Dispose()
{
leanRunner.Stop();
extraLeanRunner.Stop();
leanRunner.Dispose();
extraLeanRunner.Dispose();
}
}
}

View file

@ -2,6 +2,11 @@
using System.Reflection;
using Harmony;
// test
using Svelto.ECS;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.SimulationModeState;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Events;
@ -94,6 +99,27 @@ namespace GamecraftModdingAPI.Tests
public void OnLevelWasLoaded(int level) { }
public void OnUpdate() { }
public void OnUpdate()
{
/*
if (db != null && signalStrength != 0.0f && db.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).simulationMode == SimulationMode.Simulation)
{
//GamecraftModdingAPI.Utility.Logging.MetaDebugLog("Simulation frame update");
foreach (ref ElectricityEntityStruct powah in db.QueryEntities<ElectricityEntityStruct>(CommonExclusiveGroups.FUNCTIONAL_CUBES_IN_BOTH_SIM_AND_BUILD))
{
ref ConductiveClusterID clusterId = ref db.QueryEntity<ConductiveClusterIdStruct>(powah.ID).clusterId;
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"ID {powah.ID.entityID} unmodded values: Power* {powah.powerMultiplier} Conductivity* {powah.conductivityMultiplier} Output* {powah.outputSignalMultiplier}");
uint operatingChannel = db.QueryEntity<SignalOperatingChannelStruct>(powah.ID).operatingChannel;
if (operatingChannel != 0)
{
EGID eGID = new EGID(operatingChannel, CommonExclusiveGroups.CHANNEL_OUTPUT_SIGNAL_GROUPS + clusterId.ID);
if (db.Exists<ChannelOutputSignalDataStruct>(eGID))
{
db.QueryEntity<ChannelOutputSignalDataStruct>(eGID).outputSignal = signalStrength;
}
}
}
}*/
}
}
}

View file

@ -167,7 +167,7 @@ namespace GamecraftModdingAPI.Utility
public static void MetaLog(object obj)
{
var method = (new StackTrace()).GetFrame(1).GetMethod();
Log($"[{DateTime.Now.ToString()}][{method.DeclaringType.Name}.{method.Name}]{obj.ToString()}");
Log($"[{DateTime.Now.ToString()}][{method.DeclaringType.FullName}.{method.Name}]{obj.ToString()}");
}
// CLI logging