From 964ea3903029d3874d60924da0f36ee9d8b0c5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20Darr=C3=A9?= Date: Fri, 19 Apr 2024 16:26:51 -0400 Subject: [PATCH 1/2] =?UTF-8?q?feat:=C2=A0evolve=20lenia=20simulator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pwa/src/app/lenia/lenia.component.html | 14 ++- apps/pwa/src/app/lenia/lenia.component.ts | 93 +++++++++++++++++-- .../life-game-engine.component.html | 4 +- .../life-game-engine.component.ts | 8 +- apps/pwa/src/app/life-game/life-game.types.ts | 6 -- .../simulation-control-form.component.html} | 0 .../simulation-control-form.component.scss} | 0 .../simulation-control-form.component.ts} | 16 ++-- apps/pwa/src/app/simulation.types.ts | 5 + libs/lenia/project.json | 2 +- libs/lenia/src/lib.rs | 4 + 11 files changed, 122 insertions(+), 30 deletions(-) rename apps/pwa/src/app/{life-game/life-game-control-form/life-game-control-form.component.html => simulation-control-form/simulation-control-form.component.html} (100%) rename apps/pwa/src/app/{life-game/life-game-control-form/life-game-control-form.component.scss => simulation-control-form/simulation-control-form.component.scss} (100%) rename apps/pwa/src/app/{life-game/life-game-control-form/life-game-control-form.component.ts => simulation-control-form/simulation-control-form.component.ts} (65%) create mode 100644 apps/pwa/src/app/simulation.types.ts diff --git a/apps/pwa/src/app/lenia/lenia.component.html b/apps/pwa/src/app/lenia/lenia.component.html index 20acdb5..700ef60 100644 --- a/apps/pwa/src/app/lenia/lenia.component.html +++ b/apps/pwa/src/app/lenia/lenia.component.html @@ -34,9 +34,17 @@

Lenia

- - - + + + + diff --git a/apps/pwa/src/app/lenia/lenia.component.ts b/apps/pwa/src/app/lenia/lenia.component.ts index 3ea4215..896b36c 100644 --- a/apps/pwa/src/app/lenia/lenia.component.ts +++ b/apps/pwa/src/app/lenia/lenia.component.ts @@ -4,26 +4,107 @@ import { lenia } from '@ml/lenia'; import { HeatmapComponent } from '../heatmap/heatmap.component'; import { memory } from '@ml/lenia/lenia_bg.wasm'; import { MatTabsModule } from '@angular/material/tabs'; +import { SimulationControlFormComponent } from '../simulation-control-form/simulation-control-form.component'; +import { SimulationPlayMode } from '../simulation.types'; @Component({ selector: 'ml-lenia', standalone: true, - imports: [CommonModule, HeatmapComponent, MatTabsModule], + imports: [ + CommonModule, + HeatmapComponent, + MatTabsModule, + SimulationControlFormComponent, + ], templateUrl: './lenia.component.html', styleUrl: './lenia.component.scss', }) export class LeniaComponent implements OnInit { lenia = lenia(); - data!: number[]; + size = this.lenia.size(); + animationFrameId?: number; + + state!: number[]; + convolutedState!: number[]; + kernel!: number[]; ngOnInit(): void { - const pointer = this.lenia.state(); - this.data = Array.from(new Float64Array(memory.buffer, pointer, 64 * 64)); + const statePointer = this.lenia.state(); + const convolutedStatePointer = this.lenia.convoluted_state(); + const kernelPointer = this.lenia.convolution_kernel(); + this.state = Array.from( + new Float64Array(memory.buffer, statePointer, this.size * this.size) + ); + this.convolutedState = Array.from( + new Float64Array( + memory.buffer, + convolutedStatePointer, + this.size * this.size + ) + ); + this.kernel = Array.from( + new Float64Array(memory.buffer, kernelPointer, 26 * 26) + ); + } + + /** + * Set the game to the updated play mode + * + * @param playMode - the play mode selected by the user + */ + updatePlayMode(playMode: SimulationPlayMode) { + switch (playMode) { + case 'play': + this.play(); + break; + case 'pause': + this.pause(); + break; + } + } + + /** + * Set the game to 'play' mode + * + * It is done by starting a loop with requestAnimationFrame, and + * ticking the universe at each iteration + */ + play() { + this.run(); + + this.animationFrameId = requestAnimationFrame(this.play.bind(this)); + } + + /** + * Set the game to 'pause' mode + */ + pause() { + if (this.animationFrameId) { + cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = undefined; + } + } + + /** + * Play only one tick of the universe and render it + */ + next() { + this.run(); } run() { this.lenia.evolve(); - const pointer = this.lenia.state(); - this.data = Array.from(new Float64Array(memory.buffer, pointer, 64 * 64)); + const statePointer = this.lenia.state(); + const convolutedStatePointer = this.lenia.convoluted_state(); + this.state = Array.from( + new Float64Array(memory.buffer, statePointer, this.size * this.size) + ); + this.convolutedState = Array.from( + new Float64Array( + memory.buffer, + convolutedStatePointer, + this.size * this.size + ) + ); } } diff --git a/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.html b/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.html index 5acbe55..3cc605e 100644 --- a/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.html +++ b/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.html @@ -1,10 +1,10 @@
- + >
FPS: {{ fps || 0 }}
diff --git a/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.ts b/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.ts index 8943a17..4a80223 100644 --- a/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.ts +++ b/apps/pwa/src/app/life-game/life-game-engine/life-game-engine.component.ts @@ -8,8 +8,8 @@ import { import { CommonModule } from '@angular/common'; import { Cell, Universe } from '@ml/life_game'; import { memory } from '@ml/life_game/life_game_bg.wasm'; -import { LifeGameControlFormComponent } from '../life-game-control-form/life-game-control-form.component'; -import { LifeGamePlayMode } from '../life-game.types'; +import { SimulationControlFormComponent } from '../../simulation-control-form/simulation-control-form.component'; +import { SimulationPlayMode } from '../../simulation.types'; const CELL_SIZE = 5; // px const GRID_COLOR = '#CCCCCC'; @@ -21,7 +21,7 @@ const ALIVE_COLOR = '#000000'; standalone: true, templateUrl: './life-game-engine.component.html', styleUrl: './life-game-engine.component.scss', - imports: [CommonModule, LifeGameControlFormComponent], + imports: [CommonModule, SimulationControlFormComponent], }) export class LifeGameEngineComponent implements AfterViewInit { @Input() @@ -95,7 +95,7 @@ export class LifeGameEngineComponent implements AfterViewInit { * * @param playMode - the play mode selected by the user */ - updatePlayMode(playMode: LifeGamePlayMode) { + updatePlayMode(playMode: SimulationPlayMode) { switch (playMode) { case 'play': this.play(); diff --git a/apps/pwa/src/app/life-game/life-game.types.ts b/apps/pwa/src/app/life-game/life-game.types.ts index afac17c..df8cd9a 100644 --- a/apps/pwa/src/app/life-game/life-game.types.ts +++ b/apps/pwa/src/app/life-game/life-game.types.ts @@ -1,9 +1,3 @@ -export type LifeGamePlayMode = 'play' | 'pause'; - -export interface LifeGameControl { - playMode: LifeGamePlayMode; -} - export interface LifeGameConfig { width: number; height: number; diff --git a/apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.html b/apps/pwa/src/app/simulation-control-form/simulation-control-form.component.html similarity index 100% rename from apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.html rename to apps/pwa/src/app/simulation-control-form/simulation-control-form.component.html diff --git a/apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.scss b/apps/pwa/src/app/simulation-control-form/simulation-control-form.component.scss similarity index 100% rename from apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.scss rename to apps/pwa/src/app/simulation-control-form/simulation-control-form.component.scss diff --git a/apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.ts b/apps/pwa/src/app/simulation-control-form/simulation-control-form.component.ts similarity index 65% rename from apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.ts rename to apps/pwa/src/app/simulation-control-form/simulation-control-form.component.ts index bf9099d..911637f 100644 --- a/apps/pwa/src/app/life-game/life-game-control-form/life-game-control-form.component.ts +++ b/apps/pwa/src/app/simulation-control-form/simulation-control-form.component.ts @@ -5,11 +5,11 @@ import { MatFormFieldModule, MatLabel } from '@angular/material/form-field'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatButtonModule } from '@angular/material/button'; import { MatInputModule } from '@angular/material/input'; -import { LifeGamePlayMode } from '../life-game.types'; import { MatIcon } from '@angular/material/icon'; +import { SimulationPlayMode } from '../simulation.types'; @Component({ - selector: 'ml-life-game-control-form', + selector: 'ml-simulation-control-form', standalone: true, imports: [ CommonModule, @@ -22,19 +22,19 @@ import { MatIcon } from '@angular/material/icon'; MatLabel, MatIcon, ], - templateUrl: './life-game-control-form.component.html', - styleUrl: './life-game-control-form.component.scss', + templateUrl: './simulation-control-form.component.html', + styleUrl: './simulation-control-form.component.scss', }) -export class LifeGameControlFormComponent { +export class SimulationControlFormComponent { @Input() - defaultPlayMode: LifeGamePlayMode = 'pause'; + defaultPlayMode: SimulationPlayMode = 'pause'; @Output() - playModeChange = new EventEmitter(); + playModeChange = new EventEmitter(); @Output() next = new EventEmitter(); - playModeControl = new FormControl(this.defaultPlayMode, { + playModeControl = new FormControl(this.defaultPlayMode, { updateOn: 'change', }); } diff --git a/apps/pwa/src/app/simulation.types.ts b/apps/pwa/src/app/simulation.types.ts new file mode 100644 index 0000000..c34744c --- /dev/null +++ b/apps/pwa/src/app/simulation.types.ts @@ -0,0 +1,5 @@ +export type SimulationPlayMode = 'play' | 'pause'; + +export interface SimulationControl { + playMode: SimulationPlayMode; +} diff --git a/libs/lenia/project.json b/libs/lenia/project.json index af31046..f71fc85 100644 --- a/libs/lenia/project.json +++ b/libs/lenia/project.json @@ -9,7 +9,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "npx wasm-pack build libs/lenia --out-dir ../../dist/wasm/lenia --target bundler --release --scope ml", + "npx wasm-pack build libs/lenia --out-dir ../../dist/wasm/lenia --target bundler --scope ml", "npm i dist/wasm/lenia" ], "parallel": false, diff --git a/libs/lenia/src/lib.rs b/libs/lenia/src/lib.rs index e4d26e6..18f442e 100644 --- a/libs/lenia/src/lib.rs +++ b/libs/lenia/src/lib.rs @@ -75,6 +75,10 @@ impl Lenia { pub fn convolution_kernel(&self) -> *const f64 { self.convolution_kernel.m.as_ptr() } + + pub fn size(&self) -> usize { + self.size + } } impl Lenia { From 79d33235161afbe52e0ab5b8166cb7f9198a071e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20Darr=C3=A9?= Date: Sun, 27 Oct 2024 15:56:56 -0400 Subject: [PATCH 2/2] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 242ce4e..b0f8f1d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The installation steps presume that the next software are installed on your mach After cloning the repository and moving at the root of the folder, you can: - Install dependencies with `npm i`. -- Build the project with `npx nx build life_game` (nx will take care of the build dependency graph!). +- Build the wasm projects with `npx nx run-many --target build --projects life_game,lenia` (nx will take care of the build dependency graph!). - Run the angular app with `npx nx serve pwa`. - Go to `http://localhost:4200`