Skip to content

Instantly share code, notes, and snippets.

@mzaks
Created July 8, 2017 10:53
Show Gist options
  • Save mzaks/f11434c0633a6bea9592dc7bb12952a3 to your computer and use it in GitHub Desktop.
Save mzaks/f11434c0633a6bea9592dc7bb12952a3 to your computer and use it in GitHub Desktop.
Event Logger for Entitas-CSharp
using System;
using System.Collections.Generic;
namespace Entitas
{
public abstract class AbstractEventLogger : IEventLogger
{
private readonly Dictionary<Pool, byte> poolIdmap = new Dictionary<Pool, byte>();
private readonly Dictionary<Entity, byte> poolIdmapForEntity = new Dictionary<Entity, byte>();
protected string[] poolNames;
protected AbstractEventLogger(){}
private void onEntityCreated(Pool pool, Entity entity)
{
var poolId = poolIdmap[pool];
poolIdmapForEntity[entity] = poolId;
entity.OnComponentAdded += onComponentAdded;
entity.OnComponentRemoved += onComponentRemoved;
entity.OnComponentReplaced += onComponentReplaced;
CreateEntity(poolId, (ulong) entity._creationIndex);
}
private void onEntityDestroyed(Pool pool, Entity entity)
{
DestroyEntity(poolIdmap[pool], (ulong) entity._creationIndex);
entity.OnComponentAdded -= onComponentAdded;
entity.OnComponentRemoved -= onComponentRemoved;
entity.OnComponentReplaced -= onComponentReplaced;
poolIdmapForEntity.Remove(entity);
}
private void onComponentAdded(Entity entity, int index, IComponent component)
{
AddComponent(poolIdmapForEntity[entity], (ulong) entity._creationIndex, component.GetType().Name, component.ToString());
}
private void onComponentRemoved(Entity entity, int index, IComponent component)
{
RemoveComponent(poolIdmapForEntity[entity], (ulong) entity._creationIndex, component.GetType().Name, component.ToString());
}
private void onComponentReplaced(Entity entity, int index, IComponent previouscomponent, IComponent newcomponent)
{
ReplaceComponent(poolIdmapForEntity[entity], (ulong) entity._creationIndex, newcomponent.GetType().Name, newcomponent.ToString());
}
public abstract void WillExecute(string name);
public abstract void DidExecute(string name);
public abstract void CreateEntity(byte poolId, ulong entityId);
public abstract void DestroyEntity(byte poolId, ulong entityId);
public abstract void AddComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
public abstract void RemoveComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
public abstract void ReplaceComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
public void Initialise(params Pool[] pools)
{
if (poolIdmap.Count > 0)
{
Reset();
}
byte index = 0;
poolNames = new string[pools.Length];
foreach (var pool in pools)
{
poolNames[index] = pool.metaData.poolName;
poolIdmap[pool] = index;
index++;
pool.OnEntityCreated += onEntityCreated;
pool.OnEntityDestroyed += onEntityDestroyed;
}
}
public void Reset()
{
foreach (var pool in poolIdmap.Keys)
{
pool.OnEntityCreated -= onEntityCreated;
pool.OnEntityDestroyed += onEntityDestroyed;
}
poolIdmap.Clear();
foreach (var entity in poolIdmapForEntity.Keys)
{
entity.OnComponentAdded -= onComponentAdded;
entity.OnComponentRemoved -= onComponentRemoved;
entity.OnComponentReplaced -= onComponentReplaced;
}
poolIdmapForEntity.Clear();
}
public abstract void Start ();
public abstract string Finish (string path);
}
}
using System;
using System.Collections.Generic;
using System.IO;
using FlatBuffers;
namespace Entitas
{
public class BinaryEventLogger : AbstractEventLogger
{
public static readonly BinaryEventLogger Logger = new BinaryEventLogger();
private ulong eventId = 0;
private bool started = false;
private List<Offset<Signal>> allEvents = new List<Offset<Signal>>();
private SortedDictionary<string, List<Offset<Signal>>> systemEvents = new SortedDictionary<string, List<Offset<Signal>>>();
private SortedDictionary<EntityKey, List<Offset<Signal>>> entityEvents = new SortedDictionary<EntityKey, List<Offset<Signal>>>();
private SortedDictionary<string, List<Offset<Signal>>> componentEvents = new SortedDictionary<string, List<Offset<Signal>>>();
private FlatBufferBuilder fbb;
private Dictionary<string, StringOffset> names = new Dictionary<string, StringOffset>();
private string executingSystemName;
private double executingSystemTimestamp = double.MaxValue;
public double executionTimeLoggingThreshold = 0.0001; // -1;//
public override void WillExecute(string name)
{
executingSystemName = name;
executingSystemTimestamp = DateTime.UtcNow.Ticks / 10000000.0;
}
public override void DidExecute(string name)
{
var timestamp = DateTime.UtcNow.Ticks / 10000000.0;
if (executingSystemName == null)
{
AddSystemEvent(name, ActionType.didExecuteSystem, timestamp);
} else if (timestamp - executingSystemTimestamp > executionTimeLoggingThreshold)
{
AddSystemEvent(executingSystemName, ActionType.willExecuteSystem, executingSystemTimestamp);
AddSystemEvent(name, ActionType.didExecuteSystem, timestamp);
}
executingSystemName = null;
executingSystemTimestamp = double.MaxValue;
}
private void AddSystemEvent(string name, ActionType type, double timestamp)
{
if (!started)
{
return;
}
StringOffset nameOffset;
if (names.ContainsKey(name))
{
nameOffset = names[name];
}
else
{
nameOffset = fbb.CreateString(name);
names[name] = nameOffset;
}
Signal.StartSignal(fbb);
var longId = LongId.CreateLongId(fbb, eventId);
Signal.AddId(fbb, longId);
Signal.AddTimestamp(fbb, timestamp);
Signal.AddSystemName(fbb, nameOffset);
Signal.AddActionType(fbb, type);
eventId++;
var signalOffset = Signal.EndSignal(fbb);
allEvents.Add(signalOffset);
if (systemEvents.ContainsKey(name))
{
systemEvents[name].Add(signalOffset);
}
else
{
systemEvents[name] = new List<Offset<Signal>>{signalOffset};
}
}
public override void CreateEntity(byte poolId, ulong entityId)
{
CheckForExecutingSystem();
AddEntityEvent(poolId, entityId, ActionType.createEntity);
}
public override void DestroyEntity(byte poolId, ulong entityId)
{
CheckForExecutingSystem();
AddEntityEvent(poolId, entityId, ActionType.destroyEntity);
}
private void CheckForExecutingSystem()
{
if (executingSystemName != null)
{
AddSystemEvent(executingSystemName, ActionType.willExecuteSystem, executingSystemTimestamp);
executingSystemName = null;
executingSystemTimestamp = double.MaxValue;
}
}
private void AddEntityEvent(byte poolId, ulong entityId, ActionType type)
{
if (!started)
{
return;
}
var timestamp = DateTime.UtcNow.Ticks / 10000000.0;
Signal.StartSignal(fbb);
var longId = LongId.CreateLongId(fbb, eventId);
Signal.AddId(fbb, longId);
Signal.AddTimestamp(fbb, timestamp);
var eId = LongId.CreateLongId(fbb, entityId);
Signal.AddEntityId(fbb, eId);
var pId = ShortId.CreateShortId(fbb, poolId);
Signal.AddPoolId(fbb, pId);
Signal.AddActionType(fbb, type);
eventId++;
var signalOffset = Signal.EndSignal(fbb);
allEvents.Add(signalOffset);
var key = new EntityKey(entityId, poolId);
if (entityEvents.ContainsKey(key))
{
entityEvents[key].Add(signalOffset);
}
else
{
entityEvents[key] = new List<Offset<Signal>>{signalOffset};
}
}
public override void AddComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
CheckForExecutingSystem();
AddComponentEvent(poolId, entityId, componentName, componentDescription, ActionType.addComponent);
}
public override void RemoveComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
CheckForExecutingSystem();
AddComponentEvent(poolId, entityId, componentName, componentDescription, ActionType.removeComponent);
}
public override void ReplaceComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
CheckForExecutingSystem();
AddComponentEvent(poolId, entityId, componentName, componentDescription, ActionType.replaceComponet);
}
private void AddComponentEvent(byte poolId, ulong entityId, string componentName, string componentDescription, ActionType type)
{
if (!started)
{
return;
}
StringOffset nameOffset;
if (names.ContainsKey(componentName))
{
nameOffset = names[componentName];
}
else
{
nameOffset = fbb.CreateString(componentName);
names[componentName] = nameOffset;
}
StringOffset descriptionOffset;
if (names.ContainsKey(componentDescription))
{
descriptionOffset = names[componentDescription];
}
else
{
descriptionOffset = fbb.CreateString(componentDescription);
names[componentDescription] = nameOffset;
}
var timestamp = DateTime.UtcNow.Ticks / 10000000.0;
Signal.StartSignal(fbb);
var longId = LongId.CreateLongId(fbb, eventId);
Signal.AddId(fbb, longId);
Signal.AddTimestamp(fbb, timestamp);
var eId = LongId.CreateLongId(fbb, entityId);
Signal.AddEntityId(fbb, eId);
Signal.AddComponentName(fbb, nameOffset);
Signal.AddComponentData(fbb, descriptionOffset);
var pId = ShortId.CreateShortId(fbb, poolId);
Signal.AddPoolId(fbb, pId);
Signal.AddActionType(fbb, type);
eventId++;
var signalOffset = Signal.EndSignal(fbb);
allEvents.Add(signalOffset);
if (componentEvents.ContainsKey(componentName))
{
componentEvents[componentName].Add(signalOffset);
}
else
{
componentEvents[componentName] = new List<Offset<Signal>>{signalOffset};
}
var key = new EntityKey(entityId, poolId);
if (entityEvents.ContainsKey(key))
{
entityEvents[key].Add(signalOffset);
}
else
{
entityEvents[key] = new List<Offset<Signal>>{signalOffset};
}
}
public override void Start()
{
if (fbb == null)
{
fbb = new FlatBufferBuilder(1024);
}
started = true;
}
public override string Finish(string path)
{
if (!started)
{
return "We stoped logging";
}
var signalsVector = Session.CreateSignalsVector(fbb, allEvents.ToArray());
var componentsVector = ComponentsVector();
var systemsVector = SystemsVector();
var entityVector = EntityVector();
var poolNamesVector = PoolNames();
Session.StartSession(fbb);
Session.AddSignals(fbb, signalsVector);
Session.AddComponentSignals(fbb, componentsVector);
Session.AddEntitySignals(fbb, entityVector);
Session.AddSystemSignals(fbb, systemsVector);
Session.AddPoolNames(fbb, poolNamesVector);
var sessionOffest = Session.EndSession(fbb);
Session.FinishSessionBuffer(fbb, sessionOffest);
var data = fbb.SizedByteArray();
var file = File.Create (path + "/session"+ DateTime.UtcNow.ToString("yyyyMMddHHmmss") +".bin" );
file.Write(data, 0, data.Length);
file.Close();
fbb.Clear();
started = false;
return file.Name;
}
private VectorOffset ComponentsVector()
{
var componentSignals = new Offset<Component_Signal>[componentEvents.Count];
var i = 0;
foreach (var compName in componentEvents.Keys)
{
var vector = Component_Signal.CreateSignalsVector(fbb, componentEvents[compName].ToArray());
componentSignals[i] = Component_Signal.CreateComponent_Signal(fbb, names[compName], vector);
i++;
}
var componentsVector = Session.CreateComponentSignalsVector(fbb, componentSignals);
return componentsVector;
}
private VectorOffset SystemsVector()
{
var systemSignals = new Offset<System_Signal>[systemEvents.Count];
var i = 0;
foreach (var sysName in systemEvents.Keys)
{
var vector = System_Signal.CreateSignalsVector(fbb, systemEvents[sysName].ToArray());
systemSignals[i] = System_Signal.CreateSystem_Signal(fbb, names[sysName], vector);
i++;
}
var systemsVector = Session.CreateSystemSignalsVector(fbb, systemSignals);
return systemsVector;
}
private VectorOffset PoolNames()
{
var offsets = new StringOffset[poolNames.Length];
var i = 0;
foreach (var poolName in poolNames)
{
offsets[i] = fbb.CreateString(poolName);
i++;
}
return Session.CreatePoolNamesVector(fbb, offsets);
}
private VectorOffset EntityVector()
{
var i = 0;
var entitieSignals = new Offset<Entity_Signal>[entityEvents.Count];
foreach (var key in entityEvents.Keys)
{
var vector = Component_Signal.CreateSignalsVector(fbb, entityEvents[key].ToArray());
Entity_Signal.StartEntity_Signal(fbb);
var eId = LongId.CreateLongId(fbb, key.entityId);
Entity_Signal.AddEntityId(fbb, eId);
Entity_Signal.AddSignals(fbb, vector);
var pId = ShortId.CreateShortId(fbb, key.poolId);
Entity_Signal.AddPoolId(fbb, pId);
entitieSignals[i] = Entity_Signal.EndEntity_Signal(fbb);
i++;
}
var entityVector = Session.CreateEntitySignalsVector(fbb, entitieSignals);
return entityVector;
}
}
internal class EntityKey : IComparable<EntityKey>
{
public ulong entityId;
public byte poolId;
public EntityKey(ulong entityId, byte poolId)
{
this.entityId = entityId;
this.poolId = poolId;
}
protected bool Equals(EntityKey other)
{
return entityId == other.entityId && poolId == other.poolId;
}
public int CompareTo(EntityKey other)
{
var result = poolId.CompareTo(other.poolId);
return result == 0 ? entityId.CompareTo(other.entityId) : result;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((EntityKey) obj);
}
public override int GetHashCode()
{
unchecked
{
return (entityId.GetHashCode() * 397) ^ poolId.GetHashCode();
}
}
}
}
using System;
using UnityEngine;
namespace Entitas
{
public class ConsoleEventLogger : AbstractEventLogger
{
public static readonly ConsoleEventLogger Logger = new ConsoleEventLogger();
private bool willExecuteLogged = false;
private string currentSystem = null;
public override void WillExecute(string name)
{
currentSystem = name;
}
public override void DidExecute(string name)
{
if (willExecuteLogged)
{
Debug.Log("Did execute " + name);
}
currentSystem = null;
willExecuteLogged = false;
}
public override void CreateEntity(byte poolId, ulong entityId)
{
loggWillExecute();
Debug.Log("Created entity: " + entityId + "@" + poolNames[poolId]);
}
public override void DestroyEntity(byte poolId, ulong entityId)
{
loggWillExecute();
Debug.Log("Destroyed entity: " + entityId + "@" + poolNames[poolId]);
}
public override void AddComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
loggWillExecute();
Debug.Log("Added component [" + entityId + "@" + poolNames[poolId] + "] " + componentDescription);
}
public override void RemoveComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
loggWillExecute();
Debug.Log("Removed component [" + entityId + "@" + poolNames[poolId] + "] " + componentDescription);
}
public override void ReplaceComponent(byte poolId, ulong entityId, string componentName, string componentDescription)
{
loggWillExecute();
Debug.Log("Replaced component [" + entityId + "@" + poolNames[poolId] + "] " + componentDescription);
}
public override void Start () {}
public override string Finish (string path){
return path;
}
private void loggWillExecute()
{
if (willExecuteLogged == false && currentSystem != null)
{
Debug.Log("Will execute " + currentSystem);
willExecuteLogged = true;
}
}
}
}
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Entitas.Unity.VisualDebugging {
public enum AvgResetInterval {
Always = 1,
VeryFast = 30,
Fast = 60,
Normal = 120,
Slow = 300,
Never = int.MaxValue
}
public class DebugSystems : Systems {
public static AvgResetInterval avgResetInterval = AvgResetInterval.Never;
public int totalInitializeSystemsCount {
get {
var total = 0;
foreach(var system in _initializeSystems) {
var debugSystems = system as DebugSystems;
if(debugSystems != null) {
total += debugSystems.totalInitializeSystemsCount;
} else {
total += 1;
}
}
return total;
}
}
public int totalExecuteSystemsCount {
get {
var total = 0;
foreach(var system in _executeSystems) {
var debugSystems = system as DebugSystems;
if(debugSystems != null) {
total += debugSystems.totalExecuteSystemsCount;
} else {
total += 1;
}
}
return total;
}
}
public int initializeSystemsCount { get { return _initializeSystems.Count; } }
public int executeSystemsCount { get { return _executeSystems.Count; } }
public int totalSystemsCount { get { return _systems.Count; } }
public string name { get { return _name; } }
public GameObject container { get { return _container.gameObject; } }
public double totalDuration { get { return _totalDuration; } }
public SystemInfo[] initializeSystemInfos { get { return _initializeSystemInfos.ToArray(); } }
public SystemInfo[] executeSystemInfos { get { return _executeSystemInfos.ToArray(); } }
public bool paused;
readonly string _name;
readonly List<ISystem> _systems;
readonly Transform _container;
readonly List<SystemInfo> _initializeSystemInfos;
readonly List<SystemInfo> _executeSystemInfos;
readonly Stopwatch _stopwatch;
double _totalDuration;
public DebugSystems(string name = "Systems") {
_name = name;
_systems = new List<ISystem>();
_container = new GameObject().transform;
_container.gameObject.AddComponent<DebugSystemsBehaviour>().Init(this);
_initializeSystemInfos = new List<SystemInfo>();
_executeSystemInfos = new List<SystemInfo>();
_stopwatch = new Stopwatch();
updateName();
}
public override Systems Add(ISystem system) {
_systems.Add(system);
var debugSystems = system as DebugSystems;
if(debugSystems != null) {
debugSystems.container.transform.SetParent(_container.transform, false);
}
var systemInfo = new SystemInfo(system);
if(systemInfo.isInitializeSystems) {
_initializeSystemInfos.Add(systemInfo);
}
if(systemInfo.isExecuteSystems || systemInfo.isReactiveSystems) {
_executeSystemInfos.Add(systemInfo);
}
return base.Add(system);
}
public void ResetDurations() {
foreach(var systemInfo in _initializeSystemInfos) {
systemInfo.ResetDurations();
}
foreach(var systemInfo in _executeSystemInfos) {
systemInfo.ResetDurations();
var debugSystems = systemInfo.system as DebugSystems;
if(debugSystems != null) {
debugSystems.ResetDurations();
}
}
}
public override void Initialize() {
_totalDuration = 0;
for (int i = 0; i < _initializeSystems.Count; i++) {
var system = _initializeSystems[i];
var systemInfo = _initializeSystemInfos[i];
if(systemInfo.isActive) {
GameController.EventLogger.WillExecute(systemInfo.systemName + "_Initialize");
var duration = monitorSystemInitializeDuration(system);
_totalDuration += duration;
systemInfo.AddExecutionDuration(duration);
GameController.EventLogger.DidExecute(systemInfo.systemName + "_Initialize");
}
}
updateName();
}
public override void Execute() {
if(!paused) {
Step();
}
}
public void Step() {
_totalDuration = 0;
if(Time.frameCount % (int)avgResetInterval == 0) {
ResetDurations();
}
for (int i = 0; i < _executeSystems.Count; i++) {
var system = _executeSystems[i];
var systemInfo = _executeSystemInfos[i];
if(systemInfo.isActive) {
GameController.EventLogger.WillExecute(systemInfo.systemName);
var duration = monitorSystemExecutionDuration(system);
_totalDuration += duration;
systemInfo.AddExecutionDuration(duration);
GameController.EventLogger.DidExecute(systemInfo.systemName);
}
}
updateName();
}
double monitorSystemInitializeDuration(IInitializeSystem system) {
_stopwatch.Reset();
_stopwatch.Start();
system.Initialize();
_stopwatch.Stop();
return _stopwatch.Elapsed.TotalMilliseconds;
}
double monitorSystemExecutionDuration(IExecuteSystem system) {
_stopwatch.Reset();
_stopwatch.Start();
system.Execute();
_stopwatch.Stop();
return _stopwatch.Elapsed.TotalMilliseconds;
}
void updateName() {
if(_container != null) {
_container.name = string.Format("{0} ({1} init, {2} exe, {3:0.###} ms)",
_name, _initializeSystems.Count, _executeSystems.Count, _totalDuration);
}
}
}
}
using Entitas;
using Entitas.Unity.Serialization.Blueprints;
using UnityEngine;
public class GameController : MonoBehaviour {
public Blueprints blueprints;
Systems _systems;
public static readonly IEventLogger EventLogger = BinaryEventLogger.Logger;
void Awake() {
Application.targetFrameRate = 60;
}
void Start() {
GameRandom.core = new Rand(0);
GameRandom.view = new Rand(0);
var pools = Pools.sharedInstance;
pools.SetAllPools();
pools.AddEntityIndices();
pools.blueprints.SetBlueprints(blueprints);
EventLogger.Initialise(pools.allPools);
EventLogger.Start();
_systems = createSystems(pools);
// Suggested systems lifecycle:
// systems.Initialize() on Start
// systems.Execute() on Update
// systems.Cleanup() on Update after systems.Execute()
// systems.TearDown() on OnDestroy
_systems.Initialize();
}
void Update() {
_systems.Execute();
_systems.Cleanup();
}
void OnDestroy() {
_systems.TearDown();
EventLogger.Reset();
}
void OnApplicationQuit()
{
var fileName = EventLogger.Finish(Application.persistentDataPath);
UnityEngine.Debug.Log(fileName);
}
Systems createSystems(Pools pools) {
return new Feature("Systems")
// Initialize
.Add(pools.CreateSystem(new IncrementTickSystem()))
.Add(pools.CreateSystem(new CreatePlayerSystem()))
.Add(pools.CreateSystem(new CreateEnemySystem()))
.Add(pools.core.CreateSystem(new AddViewSystem()))
.Add(pools.bullets.CreateSystem(new AddViewFromObjectPoolSystem()))
// Input
.Add(pools.CreateSystem(new InputSystem()))
.Add(pools.input.CreateSystem(new ProcessMoveInputSystem()))
.Add(pools.input.CreateSystem(new ProcessShootInputSystem()))
.Add(pools.input.CreateSystem(new ProcessCollisionSystem()))
.Add(pools.input.CreateSystem(new SlowMotionSystem()))
// Update
.Add(pools.core.CreateSystem(new BulletCoolDownSystem()))
.Add(pools.core.CreateSystem(new StartEnemyWaveSystem()))
.Add(pools.CreateSystem(new VelocitySystem()))
.Add(pools.CreateSystem(new RenderPositionSystem()))
.Add(pools.core.CreateSystem(new CheckHealthSystem()))
.Add(pools.bullets.CreateSystem(new BulletOutOfScreenSystem()))
// Animate Destroy
.Add(pools.CreateSystem(new AnimateOutOfScreenViewSystem()))
.Add(pools.CreateSystem(new AnimateDestroyViewSystem()))
// Destroy
.Add(pools.CreateSystem(new DestroyEntitySystem()));
}
}
namespace Entitas
{
public interface IEventLogger
{
void WillExecute(string name);
void DidExecute(string name);
void CreateEntity(byte poolId, ulong entityId);
void DestroyEntity(byte poolId, ulong entityId);
void AddComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
void RemoveComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
void ReplaceComponent(byte poolId, ulong entityId, string componentName, string componentDescription);
void Initialise(params Pool[] pools);
void Start();
void Reset();
string Finish(string path);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment