|
@@ -0,0 +1,140 @@
|
|
|
|
|
+---
|
|
|
|
|
+icon: dot
|
|
|
|
|
+title: Threading
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+pocketpy organizes its state by `VM` structure.
|
|
|
|
|
+Users can have at maximum 16 `VM` instances (index from 0 to 15).
|
|
|
|
|
+Each `VM` instance can only be accessed by exactly one thread at a time.
|
|
|
|
|
+If you are trying to run two python scripts in parallel refering the same `VM` instance,
|
|
|
|
|
+you will crash it definitely.
|
|
|
|
|
+
|
|
|
|
|
+However, there are two ways to achieve multi-threading in pocketpy.
|
|
|
|
|
+
|
|
|
|
|
+One way is to use a native threading library such as `pthread`.
|
|
|
|
|
+You can wrap the multi-threading logic into a C function and bind it to pocketpy.
|
|
|
|
|
+Be careful and not to access the same `VM` instance from multiple threads at the same time.
|
|
|
|
|
+You need to lock critical resources or perform a deep copy of all needed data.
|
|
|
|
|
+
|
|
|
|
|
+## ComputeThread
|
|
|
|
|
+
|
|
|
|
|
+The other way is to use `pkpy.ComputeThread`.
|
|
|
|
|
+It is like an isolate in Dart language.
|
|
|
|
|
+`ComputeThread` is a true multi-threading model to allow you run python scripts in parallel without lock,
|
|
|
|
|
+backed by a separate `VM` instance.
|
|
|
|
|
+
|
|
|
|
|
+`ComputeThread` is highly designed for computational intensive tasks in games.
|
|
|
|
|
+For example, you can run game logic in main thread (VM 0) and run world generation in another thread (e.g. VM 1).
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+graph TD
|
|
|
|
|
+ subgraph Main Thread
|
|
|
|
|
+ A[Game Start]
|
|
|
|
|
+ B[Submit WorldGen Job]
|
|
|
|
|
+ C[Frame 1]
|
|
|
|
|
+ D[Frame 2]
|
|
|
|
|
+ E[Frame 3]
|
|
|
|
|
+ F[...]
|
|
|
|
|
+ G[Get WorldGen Result]
|
|
|
|
|
+ H[Render World]
|
|
|
|
|
+ end
|
|
|
|
|
+ subgraph WorldGen Thread
|
|
|
|
|
+ O[Generate Biomes]
|
|
|
|
|
+ P[Generate Terrain]
|
|
|
|
|
+ Q[Generate Creatures]
|
|
|
|
|
+ R[Dump Result]
|
|
|
|
|
+ end
|
|
|
|
|
+ A --> B
|
|
|
|
|
+ B --> C
|
|
|
|
|
+ C --> D
|
|
|
|
|
+ D --> E
|
|
|
|
|
+ E --> F
|
|
|
|
|
+ F --> G
|
|
|
|
|
+ G --> H
|
|
|
|
|
+
|
|
|
|
|
+ O --> P
|
|
|
|
|
+ P --> Q
|
|
|
|
|
+ Q --> R
|
|
|
|
|
+
|
|
|
|
|
+ B --> O
|
|
|
|
|
+ R --> G
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### `main.py`
|
|
|
|
|
+```python
|
|
|
|
|
+import time
|
|
|
|
|
+from pkpy import ComputeThread
|
|
|
|
|
+
|
|
|
|
|
+thread = ComputeThread(1)
|
|
|
|
|
+print("Game Start")
|
|
|
|
|
+
|
|
|
|
|
+# import worldgen.py
|
|
|
|
|
+thread.exec_blocked('from worldgen import gen_world')
|
|
|
|
|
+
|
|
|
|
|
+print("Submit WorldGen Job")
|
|
|
|
|
+thread.call('gen_world', 3, (100, 100), 10)
|
|
|
|
|
+
|
|
|
|
|
+# wait for worldgen to finish
|
|
|
|
|
+for i in range(1, 100000):
|
|
|
|
|
+ print('Frame:', i)
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+ if thread.is_done:
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+error = thread.last_error()
|
|
|
|
|
+if error is not None:
|
|
|
|
|
+ print("Error:", error)
|
|
|
|
|
+else:
|
|
|
|
|
+ retval = thread.last_retval()
|
|
|
|
|
+ biomes = retval['biomes']
|
|
|
|
|
+ terrain = retval['terrain']
|
|
|
|
|
+ creatures = retval['creatures']
|
|
|
|
|
+ print("World Generation Complete", len(biomes), len(terrain), len(creatures))
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### `worldgen.py`
|
|
|
|
|
+```python
|
|
|
|
|
+import time
|
|
|
|
|
+import random
|
|
|
|
|
+
|
|
|
|
|
+def gen_world(biome_count: int, terrain_size: tuple[int, int], creature_count: int) -> dict:
|
|
|
|
|
+ # simulate a long computation
|
|
|
|
|
+ time.sleep(3)
|
|
|
|
|
+
|
|
|
|
|
+ # generate world data
|
|
|
|
|
+ all_biomes = ["forest", "desert", "ocean", "mountain", "swamp"]
|
|
|
|
|
+ all_creatures = ["wolf", "bear", "fish", "bird", "lizard"]
|
|
|
|
|
+
|
|
|
|
|
+ width, height = terrain_size
|
|
|
|
|
+
|
|
|
|
|
+ terrain_data = [
|
|
|
|
|
+ random.randint(1, 10)
|
|
|
|
|
+ for _ in range(width * height)
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ creatures = [
|
|
|
|
|
+ {
|
|
|
|
|
+ "name": random.choice(all_creatures),
|
|
|
|
|
+ "x": random.randint(0, width - 1),
|
|
|
|
|
+ "y": random.randint(0, height - 1),
|
|
|
|
|
+ }
|
|
|
|
|
+ for i in range(creature_count)
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ "biomes": all_biomes[:biome_count],
|
|
|
|
|
+ "terrain": terrain_data,
|
|
|
|
|
+ "creatures": creatures,
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Run `main.py` and you will see the result like this:
|
|
|
|
|
+```
|
|
|
|
|
+Game Start
|
|
|
|
|
+Submit WorldGen Job
|
|
|
|
|
+Frame: 1
|
|
|
|
|
+Frame: 2
|
|
|
|
|
+Frame: 3
|
|
|
|
|
+Frame: 4
|
|
|
|
|
+World Generation Complete 3 10000 10
|
|
|
|
|
+```
|