Skip to content

Commit

Permalink
Merge branch 'shared-memory-objects' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
daneren2005 committed Apr 9, 2024
2 parents 38eab91 + 5e70bf1 commit 941f81a
Show file tree
Hide file tree
Showing 26 changed files with 1,455 additions and 26 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,47 @@ This is my first attempt to use an ECS system, and I think I tried to hard to co

https://daneren2005.github.io/ecs-sharedarraybuffer-playground/#/bitecs

## Custom ES backend by a shared memory pool
This is another version backed by @daneren2005/shared-memory-objects to allow allocating chunks of memory for each entity and then mapping entity properties to it. That library allows allocating/freeing memory backed by a SharedArrayBuffer and some simple data structures like an array backed by TypedArrays. We are only passing the heap (which wraps the SharedArrayBuffers) and the world's memory pointer to each thread and then each thread can iterate over the entities to run updates on them from that.

ie:
```
class Entity {
memory: AllocatedMemory
get x() {
return Atomics.load(this.memory.data, 0);
}
set x(value: number) {
Atomics.store(this.memory.data, 0, value);
}
constructor(heap: MemoryHeap, memory?: SharedAllocatedMemory) {
if(memory) {
// Recreating memory from another thread
this.memory = new AllocatedMemory(world.heap, config);
} else {
// Initializing the entity
this.memory = heap.allocUI32(1);
}
}
}
```

Pros:
* Can use OOP and entities as classes (obviously subjective to how you like to work)
* Easy to share data structures between threads since we have a generic pool of expandable memory

Cons
* All of the difficulty of manually managing memory in C++ without any of the speed
* Lots of boiler plate for each property to be backed by a memory location - also easy to screw up and have two properties going to one location

TODO
* Creating quad-tree or collision system in one thread and re-using in others
* Control which entities are initialized in other threads (ie: spawn-ship-system doesn't need to create a local copy of each ship to run)



## Custom ECS backed by SharedArrayBuffers
In this version, we have a quick and dirty ECS system where the components are hard coded and each SharedArrayBuffer is a large fixed length int array. Dead entity id's are recycled and re-used again. Each component is using SharedArrayBuffers and Atomics to load and update properties. Each system runs in it's own thread. All the main thread is doing every frame is looping through every entity and updating it's visual properties (ie: position, angle, etc...). As a huge number of ships are added stuff starts to not work 100% correctly since some of the sub-systems take too long to process, but visually continue to hum along nicely. That could probably be fixed by sharding the heavy systems into multiple threads. For a proof of concept I think this is good enough.

Expand Down
41 changes: 36 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"preview": "vite preview"
},
"dependencies": {
"@daneren2005/shared-memory-objects": "^0.0.2",
"bitecs": "^0.3.40",
"core-js": "^3.36.0",
"phaser": "^3.80.1",
"vue": "^3.4.21",
Expand All @@ -22,7 +24,6 @@
"@typescript-eslint/parser": "^7.1.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^12.0.0",
"bitecs": "^0.3.40",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.22.0",
"typescript": "~5.3.3",
Expand Down
7 changes: 4 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<nav>
<router-link to="/multithreaded">Multithreaded</router-link> |
<router-link to="/bitecs">BitECS</router-link> |
<router-link to="/simple">Simple Entity</router-link>
<router-link to="/multithreaded">Custom ECS (MT)</router-link> |
<router-link to="/shared-memory-objects">Entity System (MT)</router-link> |
<router-link to="/bitecs">BitECS(ST) </router-link> |
<router-link to="/simple">Simple Entity (ST)</router-link>
</nav>
<router-view/>
</template>
Expand Down
3 changes: 3 additions & 0 deletions src/math/degrees-to-radians.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function degreesToRadians(degrees: number) {
return degrees * (Math.PI / 180);
}
16 changes: 16 additions & 0 deletions src/math/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default function normalize(x: number, y: number) {
let len = x * x + y * y;
if(len > 0) {
len = 1 / Math.sqrt(len);

return {
x: x * len,
y: y * len
};
} else {
return {
x,
y
};
}
}
18 changes: 1 addition & 17 deletions src/multithreaded/systems/move-to-target-system.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import computeAngle from '@/math/compute-angle';
import { getEntitiesWithComponents } from '../entities/get-entities';
import WorldConfig from '../entities/world-config';
import normalize from '@/math/normalize';

export default function moveToTargetSystem(world: WorldConfig) {
const position = world.components.position;
Expand Down Expand Up @@ -30,21 +31,4 @@ export default function moveToTargetSystem(world: WorldConfig) {
function getMoveTowardsForce(eid: number, otherEid: number) {
return normalize(Atomics.load(position.x, otherEid) - Atomics.load(position.x, eid), Atomics.load(position.y, otherEid) - Atomics.load(position.y, eid));
}

function normalize(x: number, y: number) {
let len = x * x + y * y;
if(len > 0) {
len = 1 / Math.sqrt(len);

return {
x: x * len,
y: y * len
};
} else {
return {
x,
y
};
}
}
}
5 changes: 5 additions & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ const routes: Array<RouteRecordRaw> = [
name: 'simple',
component: () => import(/* webpackChunkName: "simple" */ '../simple/SimpleGame.vue')
},
{
path: '/shared-memory-objects',
name: 'shared-memory-objects',
component: () => import(/* webpackChunkName: "shared-memory-objects" */ '../shared-memory-objects/SharedMemoryObjects.vue')
},
{
path: '/bitecs',
name: 'bitecs',
Expand Down
Loading

0 comments on commit 941f81a

Please sign in to comment.