Skip to content

Add examples for simulations #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
62 changes: 62 additions & 0 deletions examples/simulation1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Running a simulation, part 1
----------------------------

In this example, we run a simulation (Conway's Game of Life),
where each rendered frame is one step in the simulation.
This is convenient when you want to see every step of an animation.

We could simply call this simulation_step() inside the animate function
to get one step on each frame. But in this example we wrapped
the simulation in a generator function.

Note that this example uses the bitmap context, but this can be used
with wgpu in the same way.
"""

import numpy as np

from rendercanvas.auto import RenderCanvas, loop
from scipy.signal import convolve2d


canvas = RenderCanvas()
canvas.set_update_mode("continuous", max_fps=10)


context = canvas.get_context("bitmap")


def simulation_step(grid: np.ndarray) -> np.ndarray:
# Define the 3x3 convolution kernel for neighbor count
kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])
# Count live neighbors
neighbors = convolve2d(grid, kernel, mode="same", boundary="wrap")
# Apply the rules of Game of Life
next_grid = ((grid == 1) & ((neighbors == 2) | (neighbors == 3))) | (
(grid == 0) & (neighbors == 3)
)
return next_grid.astype(np.uint8)


def simulation():
# Initialize a grid with a glider
grid = np.zeros((30, 30), dtype=np.uint8)
grid[1, 2] = grid[2, 3] = grid[3, 1] = grid[3, 2] = grid[3, 3] = 1

# Keep stepping
while True:
grid = simulation_step(grid)
yield grid


grid_generator = simulation()


@canvas.request_draw
def animate():
grid = next(grid_generator)
context.set_bitmap(grid * 255)


loop.run()
83 changes: 83 additions & 0 deletions examples/simulation2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Running a simulation, part 2
----------------------------

In this example, we run a simulation (Conway's Game of Life),
where the simulation and rendering both run at their own pace.
This is convenient when you want to run a simulation as fast as possible,
and just have the animation to 'keep an eye' on it.

To allow the simulation to run concurrently, we implememt it as an async
function that is added as a task. The animation simply renders the current
grid.

Note that this example uses the bitmap context, but this can be used
with wgpu in the same way.

Note that this works for any backend, and does not require the asyncio event loop.
"""

import numpy as np
from rendercanvas.pyside6 import RenderCanvas, loop
from scipy.signal import convolve2d
from rendercanvas.utils.asyncs import sleep as async_sleep


canvas = RenderCanvas()
canvas.set_update_mode("continuous", max_fps=20)


context = canvas.get_context("bitmap")


the_grid = np.zeros((10, 10), np.uint8)


def simulation_step(grid: np.ndarray) -> np.ndarray:
# Define the 3x3 convolution kernel for neighbor count
kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]])
# Count live neighbors
neighbors = convolve2d(grid, kernel, mode="same", boundary="wrap")
# Apply the rules of Game of Life
next_grid = ((grid == 1) & ((neighbors == 2) | (neighbors == 3))) | (
(grid == 0) & (neighbors == 3)
)
return next_grid.astype(np.uint8)


async def simulation():
global the_grid

# Initialize a grid with a glider
grid = np.zeros((30, 30), dtype=np.uint8)
grid[1, 2] = grid[2, 3] = grid[3, 1] = grid[3, 2] = grid[3, 3] = 1

# Keep stepping
while True:
grid = simulation_step(grid)

# Sleep to allow other tasks to run (and keep the window alive)
# This sleep time can be very small. We made it higher than necessary
# to deliberately slow the animation down a bit.
# If one simulation step takes a long time, you can add some sleep-calls
# inside the simulation step as well (note that the simulation_step() must
# then be made async).
await async_sleep(0.01)

# Set the grid so the animation can draw it. If the simulation
# is fast, some steps can be skipped. ìf the simulation is slow,
# the same grid may be rendered multiple times.
the_grid = grid

# You can also force a draw here, but if you want to see each frame you
# should probably use the approach shown in the simulation1.py example.
# canvas.force_draw()


@canvas.request_draw
def animate():
context.set_bitmap(the_grid * 255)


loop.add_task(simulation)
loop.run()
11 changes: 11 additions & 0 deletions examples/simulation3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Running a simulation, part 3
----------------------------

In this example, we run a simulation (Conway's Game of Life),
using the animation events.

This allows simulations that must keep up with the real world.
"""

# TODO: implement animation events