Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - HitCache/ecslite: LeoEcsLite - Lightweight C# Entity Component System framework · GitHub
Skip to content

Repository files navigation

LeoEcsLite - Lightweight C# Entity Component System framework

Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.

Important! Don't forget to use DEBUG builds for development and RELEASE builds in production: all internal error checks / exception throwing works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! LeoEcsLite API is not tread safe and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Table of content

Socials

discord

Installation

As unity module

This repository can be installed as unity module directly from git url. In this way new line should be added to Packages/manifest.json:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git",

By default last released version will be used. If you need trunk / developing version then develop name of branch should be added after hash:

"com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop",

As source

If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from releases page.

Main parts of ecs

Entity

Сontainer for components. Implemented as int:

// Creates new entity in world context.intentity=_world.NewEntity();// Any entity can be destroyed. All components will be removed first, then entity will be destroyed. world.DelEntity(entity);

Important! Entities can't live without components and will be killed automatically after last component removement.

Component

Container for user data without / with small logic inside:

structComponent1{publicintId;publicstringName;}

Components can be added / requested / removed through component pools.

System

Сontainer for logic for processing filtered entities. User class should implement IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (or other supported) interfaces:

classUserSystem:IEcsPreInitSystem,IEcsInitSystem,IEcsRunSystem,IEcsDestroySystem,IEcsPostDestroySystem{publicvoidPreInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init().}publicvoidInit(EcsSystemssystems){// Will be called once during EcsSystems.Init() call and after IEcsInitSystem.PreInit().}publicvoidRun(EcsSystemssystems){// Will be called on each EcsSystems.Run() call.}publicvoidDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and before IEcsInitSystem.PostDestroy().}publicvoidPostDestroy(EcsSystemssystems){// Will be called once during EcsSystems.Destroy() call and after IEcsInitSystem.Destroy().}}

Data sharing

Instance of any custom type can be shared between all systems:

classSharedData{publicstringPrefabsPath;}
...SharedData sharedData =newSharedData{PrefabsPath="Items/{0}"};EcsSystemssystems=newEcsSystems(world,sharedData);systems.Add(newTestSystem1()).Init();
...
class TestSystem1 :IEcsInitSystem{publicvoidInit(EcsSystemssystems){SharedDatashared=systems.GetShared<SharedData>();stringprefabPath=string.Format(shared.PrefabsPath,123);// prefabPath = "Items/123" here.}}

Special classes

EcsPool

Container for components, provides api for adding / requesting / removing components on entity:

intentity=world.NewEntity();EcsPool<Component1>pool=world.GetPool<Component1>();// Add() adds component to entity. If component already exists - exception will be raised in DEBUG.refComponent1c1=refpool.Add(entity);// Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG.refComponent1c1=refpool.Get(entity);// Del() removes component from entity. If it was last component - entity will be removed automatically too.pool.Del(entity);

Important! After removing component will be pooled and can be reused later. All fields will be reset to default values automatically.

EcsFilter

Container for keeping filtered entities with specified component list:

classWeaponSystem:IEcsInitSystem,IEcsRunSystem{publicvoidInit(EcsSystemssystems){// We want to get default world instance...EcsWorldworld=systems.GetWorld();// and create test entity...intentity=world.NewEntity();// with "Weapon" component on it.varweapons=world.GetPool<Weapon>();weapons.Add(entity);}publicvoidRun(EcsSystemssystems){EcsWorldworld=systems.GetWorld();// We want to get entities with "Weapon" and without "Health".// You can cache this filter somehow if you want.varfilter=world.Filter<Weapon>().Exc<Health>().End();// We want to get pool of "Weapon" components.// You can cache this pool somehow if you want.varweapons=world.GetPool<Weapon>();foreach(intentityinfilter){refWeaponweapon=refweapons.Get(entity);weapon.Ammo=System.Math.Max(0,weapon.Ammo-1);}}}

Additional constraints can be added with Inc<>() / Exc<>() methods.

Important: Any filter supports any amount of components, include and exclude lists can't intersect and should be unique.

EcsWorld

Root level container for all entities / components, works like isolated environment.

Important: Do not forget to call EcsWorld.Destroy() method if instance will not be used anymore.

EcsSystems

Group of systems to process EcsWorld instance:

classStartup:MonoBehaviour{EcsWorld_world;EcsSystems_systems;voidStart(){// create ecs environment._world=newEcsWorld();_systems=newEcsSystems(_world).Add(newWeaponSystem());_systems.Init();}voidUpdate(){// process all dependent systems._systems?.Run();}voidOnDestroy(){// destroy systems logical group.if(_systems!=null){_systems.Destroy();_systems=null;}// destroy world.if(_world!=null){_world.Destroy();_world=null;}}}

Important: Do not forget to call EcsSystems.Destroy() method if instance will not be used anymore.

Engine integration

Unity

Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.

Not ready yet.

Custom engine

C#7.3 or above required for this framework.

Code example - each part should be integrated in proper place of engine execution flow.

usingLeopotam.EcsLite;classEcsStartup{EcsWorld_world;EcsSystems_systems;// Initialization of ecs world and systems.voidInit(){_world=newEcsWorld();_systems=newEcsSystems(_world);_systems// register additional worlds here.// .AddWorld (customWorldInstance, "events")// register your systems here, for example:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// register components for removing here// position in registration is important,// should be after all AddWorld() registration, for example:// .DelHere<TestComponent1> ()// .DelHere<TestComponent2> ("events").Init();}// Engine update loop.voidUpdateLoop(){_systems?.Run();}// Cleanup.voidDestroy(){if(_systems!=null){_systems.Destroy();_systems=null;}if(_world!=null){_world.Destroy();_world=null;}}}

Projects powered by LeoECS Lite

With sources

Extensions

License

The software is released under the terms of the MIT license.

No personal support or any guarantees.

FAQ

I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

EcsSystems_update;EcsSystems_fixedUpdate;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world).Add(newUpdateSystem());_update.Init();_fixedUpdate=newEcsSystems(world).Add(newFixedUpdateSystem());_fixedUpdate.Init();}voidUpdate(){_update.Run();}voidFixedUpdate(){_fixedUpdate.Run();}

I copy&paste my reset components code again and again. How can I do it in other manner?

If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component:

structMyComponent:IEcsAutoReset<MyComponent>{publicintId;publicobjectLinkToAnotherComponent;publicvoidAutoReset(refMyComponentc){c.Id=2;c.LinkToAnotherComponent=null;}}

This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool.

Important: With custom AutoReset behaviour there are no any additional checks for reference-type fields, you should provide correct cleanup/init behaviour without possible memory leaks.

I use components as events that work only one frame, then remove it at last system in execution sequence. It's boring, how can I automate it?

If you want to remove one-frame components without additional custom code, you can register them at EcsSystems:

structMyOneFrameComponent{}EcsSystems_update;voidStart(){EcsWorldworld=newEcsWorld();_update=newEcsSystems(world);_update.Add(newCalculateSystem()).Add(newUpdateSystem()).DelHere<MyOneFrameComponent>().Init();}voidUpdate(){_update.Run();}

important: All one-frame components should be registered with DelHere() after all worlds registration through AddWorld(). Important: All one-frame components with specified type will be removed at position in execution flow where this component was registered with DelHere() call.

I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it?

For keeping entity somewhere you should pack it to special EcsPackedEntity or EcsPackedEntityWithWorld types:

EcsWorldworld=newEcsWorld();intentity=world.NewEntity();EcsPackedEntitypacked=world.PackEntity(entity);EcsPackedEntityWithWorldpackedWithWorld=world.PackEntityWithWorld(entity);
...if(packed.Unpack(world,outintunpacked)){// unpacked is valid and can be used.}if(packedWithWorld.Unpack(outEcsWorldunpackedWorld,outintunpackedWithWorld)){// unpackedWithWorld is valid and can be used.}

I want to add some reactive behaviour on world changes, how I can do it?

You can use LEOECSLITE_WORLD_EVENTS definition to enable custom event listeners support on worlds:

classTestWorldEventListener:IEcsWorldEventListener{publicvoidOnEntityCreated(intentity){// entity created - raises on world.NewEntity().}publicvoidOnEntityChanged(intentity){// entity changed - raises on pool.Add() / pool.Del().}publicvoidOnEntityDestroyed(intentity){// entity destroyed - raises on world.DelEntity() or last component removing.}publicvoidOnFilterCreated(EcsFilterfilter){// filter created - raises on world.Filter().End() for brand new filter.}publicvoidOnWorldResized(intnewSize){// world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call.}publicvoidOnWorldDestroyed(EcsWorldworld){// world destroyed - raises on world.Destroy().}}
...var world =newEcsWorld();varlistener=newTestWorldEventListener();world.AddEventListener(listener);

About

LeoEcsLite - Lightweight C# Entity Component System framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages