Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
14 changes: 11 additions & 3 deletions apps/pwa/src/app/lenia/lenia.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ <h2>Lenia</h2>
</div>
</mat-tab>
<mat-tab label="Simulation">
<button (click)="run()">Next</button>

<ml-heatmap [height]="64" [width]="64" [data]="data"></ml-heatmap>
<ml-simulation-control-form
(next)="next()"
(playModeChange)="updatePlayMode($event)"
></ml-simulation-control-form>
<ml-heatmap [height]="64" [width]="64" [data]="state"></ml-heatmap>
<ml-heatmap
[height]="64"
[width]="64"
[data]="convolutedState"
></ml-heatmap>
<ml-heatmap [height]="26" [width]="26" [data]="kernel"></ml-heatmap>
</mat-tab>
</mat-tab-group>
</div>
93 changes: 87 additions & 6 deletions apps/pwa/src/app/lenia/lenia.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div>
<div>
<ml-life-game-control-form
<ml-simulation-control-form
defaultPlayMode="pause"
(playModeChange)="updatePlayMode($event)"
(next)="next()"
></ml-life-game-control-form>
></ml-simulation-control-form>
</div>
<div>FPS: {{ fps || 0 }}</div>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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()
Expand Down Expand Up @@ -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();
Expand Down
6 changes: 0 additions & 6 deletions apps/pwa/src/app/life-game/life-game.types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
export type LifeGamePlayMode = 'play' | 'pause';

export interface LifeGameControl {
playMode: LifeGamePlayMode;
}

export interface LifeGameConfig {
width: number;
height: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<LifeGamePlayMode>();
playModeChange = new EventEmitter<SimulationPlayMode>();
@Output()
next = new EventEmitter<void>();

playModeControl = new FormControl<LifeGamePlayMode>(this.defaultPlayMode, {
playModeControl = new FormControl<SimulationPlayMode>(this.defaultPlayMode, {
updateOn: 'change',
});
}
5 changes: 5 additions & 0 deletions apps/pwa/src/app/simulation.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type SimulationPlayMode = 'play' | 'pause';

export interface SimulationControl {
playMode: SimulationPlayMode;
}
2 changes: 1 addition & 1 deletion libs/lenia/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions libs/lenia/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down