mirror of
				https://github.com/pocketpy/pocketpy
				synced 2025-10-31 08:50:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			154 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | |
| 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('from worldgen import gen_world')
 | |
| 
 | |
| print("Submit WorldGen Job")
 | |
| thread.submit_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
 | |
| ```
 | |
| 
 | |
| `ComputeThread` uses `pickle` module to serialize the data between threads.
 | |
| Parameters and return values must be supported by `pickle`.
 | |
| See [pickle](https://pocketpy.dev/modules/pickle/) for more details.
 | |
| 
 | |
| Since `ComputeThread` is backed by a separate `VM` instance,
 | |
| it does not share any state with the main thread
 | |
| except for the parameters you pass to it.
 | |
| Therefore, common python modules will be imported twice in each thread.
 | |
| 
 | |
| If you want to identify which VM instance the module is running in,
 | |
| you can call `pkpy.currentvm` or let your `ComputeThread` set some special flags
 | |
| before importing these modules.
 |