Merge branch 'pocketpy:main' into main

This commit is contained in:
crazybie 2025-04-26 11:09:53 +08:00 committed by GitHub
commit b162b605e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 608 additions and 65 deletions

View File

@ -15,7 +15,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
################################################### ###################################################
- uses: actions/setup-node@v3.1.1 - uses: actions/setup-node@v4
- name: Retype build - name: Retype build
run: | run: |
python scripts/gen_docs.py python scripts/gen_docs.py
@ -24,10 +24,10 @@ jobs:
retype build retype build
################################################### ###################################################
- name: Setup emsdk - name: Setup emsdk
uses: mymindstorm/setup-emsdk@v12 uses: mymindstorm/setup-emsdk@v14
with: with:
version: latest version: latest
actions-cache-folder: 'emsdk-cache' actions-cache-folder: 'emsdk-cache-v2'
- name: Compile - name: Compile
run: | run: |
bash build_web.sh bash build_web.sh

View File

@ -8,7 +8,7 @@ from typing import List, Dict
assert os.system("python prebuild.py") == 0 assert os.system("python prebuild.py") == 0
ROOT = 'include/pocketpy' ROOT = 'include/pocketpy'
PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h'] PUBLIC_HEADERS = ['config.h', 'export.h', 'vmath.h', 'pocketpy.h']
COPYRIGHT = '''/* COPYRIGHT = '''/*
* Copyright (c) 2025 blueloveTH * Copyright (c) 2025 blueloveTH

153
docs/features/threading.md Normal file
View File

@ -0,0 +1,153 @@
---
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.

View File

@ -1,12 +0,0 @@
---
icon: package
label: linalg
---
Provide `mat3x3`, `vec2`, `vec3`, `vec2i` and `vec3i` types.
This classes adopt `torch`'s naming convention. Methods with `_` suffix will modify the instance itself.
#### Source code
:::code source="../../include/typings/linalg.pyi" :::

10
docs/modules/vmath.md Normal file
View File

@ -0,0 +1,10 @@
---
icon: package
label: vmath
---
Provide vector math operations.
#### Source code
:::code source="../../include/typings/vmath.pyi" :::

View File

@ -11,5 +11,6 @@ extern const char kPythonLibs_dataclasses[];
extern const char kPythonLibs_datetime[]; extern const char kPythonLibs_datetime[];
extern const char kPythonLibs_functools[]; extern const char kPythonLibs_functools[];
extern const char kPythonLibs_heapq[]; extern const char kPythonLibs_heapq[];
extern const char kPythonLibs_linalg[];
extern const char kPythonLibs_operator[]; extern const char kPythonLibs_operator[];
extern const char kPythonLibs_typing[]; extern const char kPythonLibs_typing[];

View File

@ -18,7 +18,7 @@ void pk__add_module_pickle();
void pk__add_module_base64(); void pk__add_module_base64();
void pk__add_module_importlib(); void pk__add_module_importlib();
void pk__add_module_linalg(); void pk__add_module_vmath();
void pk__add_module_array2d(); void pk__add_module_array2d();
void pk__add_module_colorcvt(); void pk__add_module_colorcvt();

View File

@ -19,6 +19,7 @@ typedef struct py_TValue {
PyObject* _obj; PyObject* _obj;
c11_vec2 _vec2; c11_vec2 _vec2;
c11_vec2i _vec2i; c11_vec2i _vec2i;
c11_color32 _color32;
void* _ptr; void* _ptr;
}; };
} py_TValue; } py_TValue;

View File

@ -6,7 +6,7 @@
#include "pocketpy/config.h" #include "pocketpy/config.h"
#include "pocketpy/export.h" #include "pocketpy/export.h"
#include "pocketpy/linalg.h" #include "pocketpy/vmath.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -667,17 +667,19 @@ PK_API bool
/// noexcept /// noexcept
PK_API int py_dict_len(py_Ref self); PK_API int py_dict_len(py_Ref self);
/************* linalg module *************/ /************* vmath module *************/
void py_newvec2(py_OutRef out, c11_vec2); void py_newvec2(py_OutRef out, c11_vec2);
void py_newvec3(py_OutRef out, c11_vec3); void py_newvec3(py_OutRef out, c11_vec3);
void py_newvec2i(py_OutRef out, c11_vec2i); void py_newvec2i(py_OutRef out, c11_vec2i);
void py_newvec3i(py_OutRef out, c11_vec3i); void py_newvec3i(py_OutRef out, c11_vec3i);
void py_newcolor32(py_OutRef out, c11_color32);
c11_mat3x3* py_newmat3x3(py_OutRef out); c11_mat3x3* py_newmat3x3(py_OutRef out);
c11_vec2 py_tovec2(py_Ref self); c11_vec2 py_tovec2(py_Ref self);
c11_vec3 py_tovec3(py_Ref self); c11_vec3 py_tovec3(py_Ref self);
c11_vec2i py_tovec2i(py_Ref self); c11_vec2i py_tovec2i(py_Ref self);
c11_vec3i py_tovec3i(py_Ref self); c11_vec3i py_tovec3i(py_Ref self);
c11_mat3x3* py_tomat3x3(py_Ref self); c11_mat3x3* py_tomat3x3(py_Ref self);
c11_color32 py_tocolor32(py_Ref self);
/************* Others *************/ /************* Others *************/
@ -759,12 +761,13 @@ enum py_PredefinedType {
tp_ImportError, tp_ImportError,
tp_AssertionError, tp_AssertionError,
tp_KeyError, tp_KeyError,
/* linalg */ /* vmath */
tp_vec2, tp_vec2,
tp_vec3, tp_vec3,
tp_vec2i, tp_vec2i,
tp_vec3i, tp_vec3i,
tp_mat3x3, tp_mat3x3,
tp_color32,
/* array2d */ /* array2d */
tp_array2d_like, tp_array2d_like,
tp_array2d_like_iterator, tp_array2d_like_iterator,

View File

@ -33,3 +33,13 @@ typedef union c11_mat3x3 {
float m[3][3]; float m[3][3];
float data[9]; float data[9];
} c11_mat3x3; } c11_mat3x3;
typedef union c11_color32 {
struct {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
unsigned char data[4];
} c11_color32;

View File

@ -1,5 +1,5 @@
from typing import Callable, Literal, overload, Iterator, Self from typing import Callable, Literal, overload, Iterator, Self
from linalg import vec2i from vmath import vec2i
Neighborhood = Literal['Moore', 'von Neumann'] Neighborhood = Literal['Moore', 'von Neumann']

View File

@ -1,4 +1,4 @@
from linalg import vec3 from vmath import vec3
def linear_srgb_to_srgb(rgb: vec3) -> vec3: ... def linear_srgb_to_srgb(rgb: vec3) -> vec3: ...
def srgb_to_linear_srgb(rgb: vec3) -> vec3: ... def srgb_to_linear_srgb(rgb: vec3) -> vec3: ...

View File

@ -1,5 +1,5 @@
from typing import Self, Literal from typing import Self, Literal
from linalg import vec2, vec2i from vmath import vec2, vec2i
class TValue[T]: class TValue[T]:
def __new__(cls, value: T) -> Self: ... def __new__(cls, value: T) -> Self: ...
@ -35,9 +35,17 @@ class ComputeThread:
def last_error(self) -> str | None: ... def last_error(self) -> str | None: ...
def last_retval(self): ... def last_retval(self): ...
def exec(self, source: str) -> None: ... def submit_exec(self, source: str) -> None:
def eval(self, source: str) -> None: ... """Submit a job to execute some source code."""
def call(self, eval_src: str, *args, **kwargs) -> None: ...
def exec_blocked(self, source: str) -> None: ... def submit_eval(self, source: str) -> None:
def eval_blocked(self, source: str): ... """Submit a job to evaluate some source code."""
def submit_call(self, eval_src: str, *args, **kwargs) -> None:
"""Submit a job to call a function with arguments."""
def exec(self, source: str) -> None:
"""Directly execute some source code."""
def eval(self, source: str):
"""Directly evaluate some source code."""

View File

@ -180,4 +180,44 @@ class vec3(_vecF['vec3']):
def __init__(self, xyz: vec3i) -> None: ... def __init__(self, xyz: vec3i) -> None: ...
# Color32
class color32:
def __new__(cls, r: int, g: int, b: int, a: int) -> 'color32': ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
def __repr__(self) -> str: ...
@property
def r(self) -> int: ...
@property
def g(self) -> int: ...
@property
def b(self) -> int: ...
@property
def a(self) -> int: ...
def with_r(self, r: int) -> 'color32': ...
def with_g(self, g: int) -> 'color32': ...
def with_b(self, b: int) -> 'color32': ...
def with_a(self, a: int) -> 'color32': ...
@staticmethod
def from_hex(hex: str) -> 'color32': ...
@staticmethod
def from_vec3(vec: vec3) -> 'color32': ...
@staticmethod
def from_vec3i(vec: vec3i) -> 'color32': ...
def to_hex(self) -> str: ...
def to_vec3(self) -> vec3: ...
def to_vec3i(self) -> vec3i: ...
def ansi_fg(self, text: str) -> str: ...
def ansi_bg(self, text: str) -> str: ...
@staticmethod
def alpha_blend(src: color32, dst: color32 | None) -> color32: ...
def rgb(r: int, g: int, b: int) -> color32: ...
def rgba(r: int, g: int, b: int, a: float) -> color32: ...

1
python/linalg.py Normal file
View File

@ -0,0 +1 @@
from vmath import *

View File

@ -1,5 +1,5 @@
from .function import gen_function from .function import gen_function
from .converters import get_converter, set_linalg_converter, set_enum_converters from .converters import get_converter, set_vmath_converter, set_enum_converters
from .writer import Writer from .writer import Writer
from .struct import gen_struct from .struct import gen_struct
from .enum import gen_enum from .enum import gen_enum

View File

@ -149,7 +149,7 @@ for t in LINALG_TYPES:
_CONVERTERS['void'] = VoidConverter('void') _CONVERTERS['void'] = VoidConverter('void')
_CONVERTERS['c11_array2d'] = StructConverter('c11_array2d', 'tp_array2d') _CONVERTERS['c11_array2d'] = StructConverter('c11_array2d', 'tp_array2d')
def set_linalg_converter(T: str, py_T: str): def set_vmath_converter(T: str, py_T: str):
assert py_T in LINALG_TYPES assert py_T in LINALG_TYPES
_CONVERTERS[T] = BuiltinVectorConverter(T, py_T) _CONVERTERS[T] = BuiltinVectorConverter(T, py_T)

View File

@ -26,7 +26,7 @@ class Library:
w, pyi_w = Writer(), Writer() w, pyi_w = Writer(), Writer()
pyi_w.write('from linalg import vec2, vec3, vec2i, vec3i, mat3x3') pyi_w.write('from vmath import vec2, vec3, vec2i, vec3i, mat3x3')
pyi_w.write('from typing import overload') pyi_w.write('from typing import overload')
pyi_w.write('intptr = int') pyi_w.write('intptr = int')
pyi_w.write('') pyi_w.write('')

View File

@ -1,6 +1,6 @@
import pcpp import pcpp
import pycparser import pycparser
from c_bind import Library, set_linalg_converter, set_enum_converters from c_bind import Library, set_vmath_converter, set_enum_converters
from c_bind.meta import Header from c_bind.meta import Header
import os import os
@ -18,8 +18,8 @@ header.remove_functions({'b2CreateTimer', 'b2Hash', 'b2DefaultDebugDraw'})
lib = Library.from_header('box2d', header) lib = Library.from_header('box2d', header)
set_linalg_converter('b2Vec2', 'vec2') set_vmath_converter('b2Vec2', 'vec2')
set_linalg_converter('b2Vec3', 'vec3') set_vmath_converter('b2Vec3', 'vec3')
set_enum_converters([enum.name for enum in lib.enums]) set_enum_converters([enum.name for enum in lib.enums])

View File

@ -1,12 +1,12 @@
import json import json
from c_bind import Library, set_linalg_converter from c_bind import Library, set_vmath_converter
with open('../3rd/raylib/parser/output/raylib_api.json') as f: with open('../3rd/raylib/parser/output/raylib_api.json') as f:
data = json.load(f) data = json.load(f)
lib = Library.from_raylib(data) lib = Library.from_raylib(data)
set_linalg_converter('Vector2', 'vec2') set_vmath_converter('Vector2', 'vec2')
set_linalg_converter('Vector3', 'vec3') set_vmath_converter('Vector3', 'vec3')
lib.build( lib.build(
includes=['raylib.h'], includes=['raylib.h'],

View File

@ -9,6 +9,7 @@ const char kPythonLibs_dataclasses[] = "def _get_annotations(cls: type):\n in
const char kPythonLibs_datetime[] = "from time import localtime\nimport operator\n\nclass timedelta:\n def __init__(self, days=0, seconds=0):\n self.days = days\n self.seconds = seconds\n\n def __repr__(self):\n return f\"datetime.timedelta(days={self.days}, seconds={self.seconds})\"\n\n def __eq__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) == (other.days, other.seconds)\n\n def __ne__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) != (other.days, other.seconds)\n\n\nclass date:\n def __init__(self, year: int, month: int, day: int):\n self.year = year\n self.month = month\n self.day = day\n\n @staticmethod\n def today():\n t = localtime()\n return date(t.tm_year, t.tm_mon, t.tm_mday)\n \n def __cmp(self, other, op):\n if not isinstance(other, date):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n return op(self.day, other.day)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n\n def __lt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.lt)\n\n def __le__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.le)\n\n def __gt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.gt)\n\n def __ge__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.ge)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02}\"\n\n def __repr__(self):\n return f\"datetime.date({self.year}, {self.month}, {self.day})\"\n\n\nclass datetime(date):\n def __init__(self, year: int, month: int, day: int, hour: int, minute: int, second: int):\n super().__init__(year, month, day)\n # Validate and set hour, minute, and second\n if not 0 <= hour <= 23:\n raise ValueError(\"Hour must be between 0 and 23\")\n self.hour = hour\n if not 0 <= minute <= 59:\n raise ValueError(\"Minute must be between 0 and 59\")\n self.minute = minute\n if not 0 <= second <= 59:\n raise ValueError(\"Second must be between 0 and 59\")\n self.second = second\n\n def date(self) -> date:\n return date(self.year, self.month, self.day)\n\n @staticmethod\n def now():\n t = localtime()\n tm_sec = t.tm_sec\n if tm_sec == 60:\n tm_sec = 59\n return datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, tm_sec)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02} {self.hour:02}:{self.minute:02}:{self.second:02}\"\n\n def __repr__(self):\n return f\"datetime.datetime({self.year}, {self.month}, {self.day}, {self.hour}, {self.minute}, {self.second})\"\n\n def __cmp(self, other, op):\n if not isinstance(other, datetime):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n if self.day != other.day:\n return op(self.day, other.day)\n if self.hour != other.hour:\n return op(self.hour, other.hour)\n if self.minute != other.minute:\n return op(self.minute, other.minute)\n return op(self.second, other.second)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n \n def __lt__(self, other) -> bool:\n return self.__cmp(other, operator.lt)\n \n def __le__(self, other) -> bool:\n return self.__cmp(other, operator.le)\n \n def __gt__(self, other) -> bool:\n return self.__cmp(other, operator.gt)\n \n def __ge__(self, other) -> bool:\n return self.__cmp(other, operator.ge)\n\n\n"; const char kPythonLibs_datetime[] = "from time import localtime\nimport operator\n\nclass timedelta:\n def __init__(self, days=0, seconds=0):\n self.days = days\n self.seconds = seconds\n\n def __repr__(self):\n return f\"datetime.timedelta(days={self.days}, seconds={self.seconds})\"\n\n def __eq__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) == (other.days, other.seconds)\n\n def __ne__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) != (other.days, other.seconds)\n\n\nclass date:\n def __init__(self, year: int, month: int, day: int):\n self.year = year\n self.month = month\n self.day = day\n\n @staticmethod\n def today():\n t = localtime()\n return date(t.tm_year, t.tm_mon, t.tm_mday)\n \n def __cmp(self, other, op):\n if not isinstance(other, date):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n return op(self.day, other.day)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n\n def __lt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.lt)\n\n def __le__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.le)\n\n def __gt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.gt)\n\n def __ge__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.ge)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02}\"\n\n def __repr__(self):\n return f\"datetime.date({self.year}, {self.month}, {self.day})\"\n\n\nclass datetime(date):\n def __init__(self, year: int, month: int, day: int, hour: int, minute: int, second: int):\n super().__init__(year, month, day)\n # Validate and set hour, minute, and second\n if not 0 <= hour <= 23:\n raise ValueError(\"Hour must be between 0 and 23\")\n self.hour = hour\n if not 0 <= minute <= 59:\n raise ValueError(\"Minute must be between 0 and 59\")\n self.minute = minute\n if not 0 <= second <= 59:\n raise ValueError(\"Second must be between 0 and 59\")\n self.second = second\n\n def date(self) -> date:\n return date(self.year, self.month, self.day)\n\n @staticmethod\n def now():\n t = localtime()\n tm_sec = t.tm_sec\n if tm_sec == 60:\n tm_sec = 59\n return datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, tm_sec)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02} {self.hour:02}:{self.minute:02}:{self.second:02}\"\n\n def __repr__(self):\n return f\"datetime.datetime({self.year}, {self.month}, {self.day}, {self.hour}, {self.minute}, {self.second})\"\n\n def __cmp(self, other, op):\n if not isinstance(other, datetime):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n if self.day != other.day:\n return op(self.day, other.day)\n if self.hour != other.hour:\n return op(self.hour, other.hour)\n if self.minute != other.minute:\n return op(self.minute, other.minute)\n return op(self.second, other.second)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n \n def __lt__(self, other) -> bool:\n return self.__cmp(other, operator.lt)\n \n def __le__(self, other) -> bool:\n return self.__cmp(other, operator.le)\n \n def __gt__(self, other) -> bool:\n return self.__cmp(other, operator.gt)\n \n def __ge__(self, other) -> bool:\n return self.__cmp(other, operator.ge)\n\n\n";
const char kPythonLibs_functools[] = "class cache:\n def __init__(self, f):\n self.f = f\n self.cache = {}\n\n def __call__(self, *args):\n if args not in self.cache:\n self.cache[args] = self.f(*args)\n return self.cache[args]\n \ndef reduce(function, sequence, initial=...):\n it = iter(sequence)\n if initial is ...:\n try:\n value = next(it)\n except StopIteration:\n raise TypeError(\"reduce() of empty sequence with no initial value\")\n else:\n value = initial\n for element in it:\n value = function(value, element)\n return value\n\nclass partial:\n def __init__(self, f, *args, **kwargs):\n self.f = f\n if not callable(f):\n raise TypeError(\"the first argument must be callable\")\n self.args = args\n self.kwargs = kwargs\n\n def __call__(self, *args, **kwargs):\n kwargs.update(self.kwargs)\n return self.f(*self.args, *args, **kwargs)\n\n"; const char kPythonLibs_functools[] = "class cache:\n def __init__(self, f):\n self.f = f\n self.cache = {}\n\n def __call__(self, *args):\n if args not in self.cache:\n self.cache[args] = self.f(*args)\n return self.cache[args]\n \ndef reduce(function, sequence, initial=...):\n it = iter(sequence)\n if initial is ...:\n try:\n value = next(it)\n except StopIteration:\n raise TypeError(\"reduce() of empty sequence with no initial value\")\n else:\n value = initial\n for element in it:\n value = function(value, element)\n return value\n\nclass partial:\n def __init__(self, f, *args, **kwargs):\n self.f = f\n if not callable(f):\n raise TypeError(\"the first argument must be callable\")\n self.args = args\n self.kwargs = kwargs\n\n def __call__(self, *args, **kwargs):\n kwargs.update(self.kwargs)\n return self.f(*self.args, *args, **kwargs)\n\n";
const char kPythonLibs_heapq[] = "# Heap queue algorithm (a.k.a. priority queue)\ndef heappush(heap, item):\n \"\"\"Push item onto heap, maintaining the heap invariant.\"\"\"\n heap.append(item)\n _siftdown(heap, 0, len(heap)-1)\n\ndef heappop(heap):\n \"\"\"Pop the smallest item off the heap, maintaining the heap invariant.\"\"\"\n lastelt = heap.pop() # raises appropriate IndexError if heap is empty\n if heap:\n returnitem = heap[0]\n heap[0] = lastelt\n _siftup(heap, 0)\n return returnitem\n return lastelt\n\ndef heapreplace(heap, item):\n \"\"\"Pop and return the current smallest value, and add the new item.\n\n This is more efficient than heappop() followed by heappush(), and can be\n more appropriate when using a fixed-size heap. Note that the value\n returned may be larger than item! That constrains reasonable uses of\n this routine unless written as part of a conditional replacement:\n\n if item > heap[0]:\n item = heapreplace(heap, item)\n \"\"\"\n returnitem = heap[0] # raises appropriate IndexError if heap is empty\n heap[0] = item\n _siftup(heap, 0)\n return returnitem\n\ndef heappushpop(heap, item):\n \"\"\"Fast version of a heappush followed by a heappop.\"\"\"\n if heap and heap[0] < item:\n item, heap[0] = heap[0], item\n _siftup(heap, 0)\n return item\n\ndef heapify(x):\n \"\"\"Transform list into a heap, in-place, in O(len(x)) time.\"\"\"\n n = len(x)\n # Transform bottom-up. The largest index there's any point to looking at\n # is the largest with a child index in-range, so must have 2*i + 1 < n,\n # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so\n # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is\n # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.\n for i in reversed(range(n//2)):\n _siftup(x, i)\n\n# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos\n# is the index of a leaf with a possibly out-of-order value. Restore the\n# heap invariant.\ndef _siftdown(heap, startpos, pos):\n newitem = heap[pos]\n # Follow the path to the root, moving parents down until finding a place\n # newitem fits.\n while pos > startpos:\n parentpos = (pos - 1) >> 1\n parent = heap[parentpos]\n if newitem < parent:\n heap[pos] = parent\n pos = parentpos\n continue\n break\n heap[pos] = newitem\n\ndef _siftup(heap, pos):\n endpos = len(heap)\n startpos = pos\n newitem = heap[pos]\n # Bubble up the smaller child until hitting a leaf.\n childpos = 2*pos + 1 # leftmost child position\n while childpos < endpos:\n # Set childpos to index of smaller child.\n rightpos = childpos + 1\n if rightpos < endpos and not heap[childpos] < heap[rightpos]:\n childpos = rightpos\n # Move the smaller child up.\n heap[pos] = heap[childpos]\n pos = childpos\n childpos = 2*pos + 1\n # The leaf at pos is empty now. Put newitem there, and bubble it up\n # to its final resting place (by sifting its parents down).\n heap[pos] = newitem\n _siftdown(heap, startpos, pos)"; const char kPythonLibs_heapq[] = "# Heap queue algorithm (a.k.a. priority queue)\ndef heappush(heap, item):\n \"\"\"Push item onto heap, maintaining the heap invariant.\"\"\"\n heap.append(item)\n _siftdown(heap, 0, len(heap)-1)\n\ndef heappop(heap):\n \"\"\"Pop the smallest item off the heap, maintaining the heap invariant.\"\"\"\n lastelt = heap.pop() # raises appropriate IndexError if heap is empty\n if heap:\n returnitem = heap[0]\n heap[0] = lastelt\n _siftup(heap, 0)\n return returnitem\n return lastelt\n\ndef heapreplace(heap, item):\n \"\"\"Pop and return the current smallest value, and add the new item.\n\n This is more efficient than heappop() followed by heappush(), and can be\n more appropriate when using a fixed-size heap. Note that the value\n returned may be larger than item! That constrains reasonable uses of\n this routine unless written as part of a conditional replacement:\n\n if item > heap[0]:\n item = heapreplace(heap, item)\n \"\"\"\n returnitem = heap[0] # raises appropriate IndexError if heap is empty\n heap[0] = item\n _siftup(heap, 0)\n return returnitem\n\ndef heappushpop(heap, item):\n \"\"\"Fast version of a heappush followed by a heappop.\"\"\"\n if heap and heap[0] < item:\n item, heap[0] = heap[0], item\n _siftup(heap, 0)\n return item\n\ndef heapify(x):\n \"\"\"Transform list into a heap, in-place, in O(len(x)) time.\"\"\"\n n = len(x)\n # Transform bottom-up. The largest index there's any point to looking at\n # is the largest with a child index in-range, so must have 2*i + 1 < n,\n # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so\n # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is\n # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.\n for i in reversed(range(n//2)):\n _siftup(x, i)\n\n# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos\n# is the index of a leaf with a possibly out-of-order value. Restore the\n# heap invariant.\ndef _siftdown(heap, startpos, pos):\n newitem = heap[pos]\n # Follow the path to the root, moving parents down until finding a place\n # newitem fits.\n while pos > startpos:\n parentpos = (pos - 1) >> 1\n parent = heap[parentpos]\n if newitem < parent:\n heap[pos] = parent\n pos = parentpos\n continue\n break\n heap[pos] = newitem\n\ndef _siftup(heap, pos):\n endpos = len(heap)\n startpos = pos\n newitem = heap[pos]\n # Bubble up the smaller child until hitting a leaf.\n childpos = 2*pos + 1 # leftmost child position\n while childpos < endpos:\n # Set childpos to index of smaller child.\n rightpos = childpos + 1\n if rightpos < endpos and not heap[childpos] < heap[rightpos]:\n childpos = rightpos\n # Move the smaller child up.\n heap[pos] = heap[childpos]\n pos = childpos\n childpos = 2*pos + 1\n # The leaf at pos is empty now. Put newitem there, and bubble it up\n # to its final resting place (by sifting its parents down).\n heap[pos] = newitem\n _siftdown(heap, startpos, pos)";
const char kPythonLibs_linalg[] = "from vmath import *";
const char kPythonLibs_operator[] = "# https://docs.python.org/3/library/operator.html#mapping-operators-to-functions\n\ndef le(a, b): return a <= b\ndef lt(a, b): return a < b\ndef ge(a, b): return a >= b\ndef gt(a, b): return a > b\ndef eq(a, b): return a == b\ndef ne(a, b): return a != b\n\ndef and_(a, b): return a & b\ndef or_(a, b): return a | b\ndef xor(a, b): return a ^ b\ndef invert(a): return ~a\ndef lshift(a, b): return a << b\ndef rshift(a, b): return a >> b\n\ndef is_(a, b): return a is b\ndef is_not(a, b): return a is not b\ndef not_(a): return not a\ndef truth(a): return bool(a)\ndef contains(a, b): return b in a\n\ndef add(a, b): return a + b\ndef sub(a, b): return a - b\ndef mul(a, b): return a * b\ndef truediv(a, b): return a / b\ndef floordiv(a, b): return a // b\ndef mod(a, b): return a % b\ndef pow(a, b): return a ** b\ndef neg(a): return -a\ndef matmul(a, b): return a @ b\n\ndef getitem(a, b): return a[b]\ndef setitem(a, b, c): a[b] = c\ndef delitem(a, b): del a[b]\n\ndef iadd(a, b): a += b; return a\ndef isub(a, b): a -= b; return a\ndef imul(a, b): a *= b; return a\ndef itruediv(a, b): a /= b; return a\ndef ifloordiv(a, b): a //= b; return a\ndef imod(a, b): a %= b; return a\n# def ipow(a, b): a **= b; return a\n# def imatmul(a, b): a @= b; return a\ndef iand(a, b): a &= b; return a\ndef ior(a, b): a |= b; return a\ndef ixor(a, b): a ^= b; return a\ndef ilshift(a, b): a <<= b; return a\ndef irshift(a, b): a >>= b; return a\n"; const char kPythonLibs_operator[] = "# https://docs.python.org/3/library/operator.html#mapping-operators-to-functions\n\ndef le(a, b): return a <= b\ndef lt(a, b): return a < b\ndef ge(a, b): return a >= b\ndef gt(a, b): return a > b\ndef eq(a, b): return a == b\ndef ne(a, b): return a != b\n\ndef and_(a, b): return a & b\ndef or_(a, b): return a | b\ndef xor(a, b): return a ^ b\ndef invert(a): return ~a\ndef lshift(a, b): return a << b\ndef rshift(a, b): return a >> b\n\ndef is_(a, b): return a is b\ndef is_not(a, b): return a is not b\ndef not_(a): return not a\ndef truth(a): return bool(a)\ndef contains(a, b): return b in a\n\ndef add(a, b): return a + b\ndef sub(a, b): return a - b\ndef mul(a, b): return a * b\ndef truediv(a, b): return a / b\ndef floordiv(a, b): return a // b\ndef mod(a, b): return a % b\ndef pow(a, b): return a ** b\ndef neg(a): return -a\ndef matmul(a, b): return a @ b\n\ndef getitem(a, b): return a[b]\ndef setitem(a, b, c): a[b] = c\ndef delitem(a, b): del a[b]\n\ndef iadd(a, b): a += b; return a\ndef isub(a, b): a -= b; return a\ndef imul(a, b): a *= b; return a\ndef itruediv(a, b): a /= b; return a\ndef ifloordiv(a, b): a //= b; return a\ndef imod(a, b): a %= b; return a\n# def ipow(a, b): a **= b; return a\n# def imatmul(a, b): a @= b; return a\ndef iand(a, b): a &= b; return a\ndef ior(a, b): a |= b; return a\ndef ixor(a, b): a ^= b; return a\ndef ilshift(a, b): a <<= b; return a\ndef irshift(a, b): a >>= b; return a\n";
const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\nTypeAlias = _PLACEHOLDER\nNewType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\nIterator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\nNever = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n\n# exhaustiveness checking\nassert_never = lambda x: x\n"; const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\nTypeAlias = _PLACEHOLDER\nNewType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\nIterator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\nNever = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n\n# exhaustiveness checking\nassert_never = lambda x: x\n";
@ -22,6 +23,7 @@ const char* load_kPythonLib(const char* name) {
if (strcmp(name, "datetime") == 0) return kPythonLibs_datetime; if (strcmp(name, "datetime") == 0) return kPythonLibs_datetime;
if (strcmp(name, "functools") == 0) return kPythonLibs_functools; if (strcmp(name, "functools") == 0) return kPythonLibs_functools;
if (strcmp(name, "heapq") == 0) return kPythonLibs_heapq; if (strcmp(name, "heapq") == 0) return kPythonLibs_heapq;
if (strcmp(name, "linalg") == 0) return kPythonLibs_linalg;
if (strcmp(name, "operator") == 0) return kPythonLibs_operator; if (strcmp(name, "operator") == 0) return kPythonLibs_operator;
if (strcmp(name, "typing") == 0) return kPythonLibs_typing; if (strcmp(name, "typing") == 0) return kPythonLibs_typing;
return NULL; return NULL;

View File

@ -12,10 +12,10 @@ static bool _stable_sort_merge(char* a,
int (*f_lt)(const void* a, const void* b, void* extra), int (*f_lt)(const void* a, const void* b, void* extra),
void* extra) { void* extra) {
while(a < a_end && b < b_end) { while(a < a_end && b < b_end) {
int res = f_lt(a, b, extra); int res = f_lt(b, a, extra);
// check error // check error
if(res == -1) return false; if(res == -1) return false;
if(res) { if(res == 0) { // !(b<a) -> (b>=a) -> (a<=b)
memcpy(r, a, elem_size); memcpy(r, a, elem_size);
a += elem_size; a += elem_size;
} else { } else {

View File

@ -213,7 +213,7 @@ void VM__ctor(VM* self) {
py_newnotimplemented(py_emplacedict(&self->builtins, py_name("NotImplemented"))); py_newnotimplemented(py_emplacedict(&self->builtins, py_name("NotImplemented")));
pk__add_module_linalg(); pk__add_module_vmath();
pk__add_module_array2d(); pk__add_module_array2d();
pk__add_module_colorcvt(); pk__add_module_colorcvt();

View File

@ -774,7 +774,7 @@ static void register_array2d_like(py_Ref mod) {
py_bindmethod(type, "convolve", array2d_like_convolve); py_bindmethod(type, "convolve", array2d_like_convolve);
const char* scc = const char* scc =
"\ndef get_connected_components(self, value: T, neighborhood: Neighborhood) -> tuple[array2d[int], int]:\n from collections import deque\n from linalg import vec2i\n\n DIRS = [vec2i.LEFT, vec2i.RIGHT, vec2i.UP, vec2i.DOWN]\n assert neighborhood in ['Moore', 'von Neumann']\n\n if neighborhood == 'Moore':\n DIRS.extend([\n vec2i.LEFT+vec2i.UP,\n vec2i.RIGHT+vec2i.UP,\n vec2i.LEFT+vec2i.DOWN,\n vec2i.RIGHT+vec2i.DOWN\n ])\n\n visited = array2d[int](self.width, self.height, default=0)\n queue = deque()\n count = 0\n for y in range(self.height):\n for x in range(self.width):\n if visited[x, y] or self[x, y] != value:\n continue\n count += 1\n queue.append((x, y))\n visited[x, y] = count\n while queue:\n cx, cy = queue.popleft()\n for dx, dy in DIRS:\n nx, ny = cx+dx, cy+dy\n if self.is_valid(nx, ny) and not visited[nx, ny] and self[nx, ny] == value:\n queue.append((nx, ny))\n visited[nx, ny] = count\n return visited, count\n\narray2d_like.get_connected_components = get_connected_components\ndel get_connected_components\n"; "\ndef get_connected_components(self, value: T, neighborhood: Neighborhood) -> tuple[array2d[int], int]:\n from collections import deque\n from vmath import vec2i\n\n DIRS = [vec2i.LEFT, vec2i.RIGHT, vec2i.UP, vec2i.DOWN]\n assert neighborhood in ['Moore', 'von Neumann']\n\n if neighborhood == 'Moore':\n DIRS.extend([\n vec2i.LEFT+vec2i.UP,\n vec2i.RIGHT+vec2i.UP,\n vec2i.LEFT+vec2i.DOWN,\n vec2i.RIGHT+vec2i.DOWN\n ])\n\n visited = array2d[int](self.width, self.height, default=0)\n queue = deque()\n count = 0\n for y in range(self.height):\n for x in range(self.width):\n if visited[x, y] or self[x, y] != value:\n continue\n count += 1\n queue.append((x, y))\n visited[x, y] = count\n while queue:\n cx, cy = queue.popleft()\n for dx, dy in DIRS:\n nx, ny = cx+dx, cy+dy\n if self.is_valid(nx, ny) and not visited[nx, ny] and self[nx, ny] == value:\n queue.append((nx, ny))\n visited[nx, ny] = count\n return visited, count\n\narray2d_like.get_connected_components = get_connected_components\ndel get_connected_components\n";
if(!py_exec(scc, "array2d.py", EXEC_MODE, mod)) { if(!py_exec(scc, "array2d.py", EXEC_MODE, mod)) {
py_printexc(); py_printexc();
c11__abort("failed to execute array2d.py"); c11__abort("failed to execute array2d.py");

View File

@ -271,7 +271,7 @@ __ERROR:
return (c11_thrd_retval_t)0; return (c11_thrd_retval_t)0;
} }
static bool ComputeThread_exec(int argc, py_Ref argv) { static bool ComputeThread_submit_exec(int argc, py_Ref argv) {
PY_CHECK_ARGC(2); PY_CHECK_ARGC(2);
c11_ComputeThread* self = py_touserdata(py_arg(0)); c11_ComputeThread* self = py_touserdata(py_arg(0));
if(!self->is_done) return OSError("thread is not done yet"); if(!self->is_done) return OSError("thread is not done yet");
@ -294,7 +294,7 @@ static bool ComputeThread_exec(int argc, py_Ref argv) {
return true; return true;
} }
static bool ComputeThread_eval(int argc, py_Ref argv) { static bool ComputeThread_submit_eval(int argc, py_Ref argv) {
PY_CHECK_ARGC(2); PY_CHECK_ARGC(2);
c11_ComputeThread* self = py_touserdata(py_arg(0)); c11_ComputeThread* self = py_touserdata(py_arg(0));
if(!self->is_done) return OSError("thread is not done yet"); if(!self->is_done) return OSError("thread is not done yet");
@ -317,7 +317,7 @@ static bool ComputeThread_eval(int argc, py_Ref argv) {
return true; return true;
} }
static bool ComputeThread_call(int argc, py_Ref argv) { static bool ComputeThread_submit_call(int argc, py_Ref argv) {
PY_CHECK_ARGC(4); PY_CHECK_ARGC(4);
c11_ComputeThread* self = py_touserdata(py_arg(0)); c11_ComputeThread* self = py_touserdata(py_arg(0));
if(!self->is_done) return OSError("thread is not done yet"); if(!self->is_done) return OSError("thread is not done yet");
@ -383,7 +383,7 @@ __ERROR:
return false; return false;
} }
static bool ComputeThread_exec_blocked(int argc, py_Ref argv) { static bool ComputeThread_exec(int argc, py_Ref argv) {
PY_CHECK_ARGC(2); PY_CHECK_ARGC(2);
c11_ComputeThread* self = py_touserdata(py_arg(0)); c11_ComputeThread* self = py_touserdata(py_arg(0));
PY_CHECK_ARG_TYPE(1, tp_str); PY_CHECK_ARG_TYPE(1, tp_str);
@ -391,7 +391,7 @@ static bool ComputeThread_exec_blocked(int argc, py_Ref argv) {
return c11_ComputeThread__exec_blocked(self, source, EXEC_MODE); return c11_ComputeThread__exec_blocked(self, source, EXEC_MODE);
} }
static bool ComputeThread_eval_blocked(int argc, py_Ref argv) { static bool ComputeThread_eval(int argc, py_Ref argv) {
PY_CHECK_ARGC(2); PY_CHECK_ARGC(2);
c11_ComputeThread* self = py_touserdata(py_arg(0)); c11_ComputeThread* self = py_touserdata(py_arg(0));
PY_CHECK_ARG_TYPE(1, tp_str); PY_CHECK_ARG_TYPE(1, tp_str);
@ -409,12 +409,12 @@ static void pk_ComputeThread__register(py_Ref mod) {
py_bindmethod(type, "last_error", ComputeThread_last_error); py_bindmethod(type, "last_error", ComputeThread_last_error);
py_bindmethod(type, "last_retval", ComputeThread_last_retval); py_bindmethod(type, "last_retval", ComputeThread_last_retval);
py_bindmethod(type, "submit_exec", ComputeThread_submit_exec);
py_bindmethod(type, "submit_eval", ComputeThread_submit_eval);
py_bind(py_tpobject(type), "submit_call(self, eval_src, *args, **kwargs)", ComputeThread_submit_call);
py_bindmethod(type, "exec", ComputeThread_exec); py_bindmethod(type, "exec", ComputeThread_exec);
py_bindmethod(type, "eval", ComputeThread_eval); py_bindmethod(type, "eval", ComputeThread_eval);
py_bind(py_tpobject(type), "call(self, eval_src, *args, **kwargs)", ComputeThread_call);
py_bindmethod(type, "exec_blocked", ComputeThread_exec_blocked);
py_bindmethod(type, "eval_blocked", ComputeThread_eval_blocked);
} }
void pk__add_module_pkpy() { void pk__add_module_pkpy() {

View File

@ -1,3 +1,4 @@
#include "pocketpy/vmath.h"
#include "pocketpy/pocketpy.h" #include "pocketpy/pocketpy.h"
#include "pocketpy/common/sstream.h" #include "pocketpy/common/sstream.h"
@ -836,26 +837,281 @@ static bool vec3__with_xy(int argc, py_Ref argv) {
return true; return true;
} }
void pk__add_module_linalg() { /* Color32 */
py_Ref mod = py_newmodule("linalg"); void py_newcolor32(py_OutRef out, c11_color32 color) {
out->type = tp_color32;
out->is_ptr = false;
out->_color32 = color;
}
c11_color32 py_tocolor32(py_Ref obj) {
assert(obj->type == tp_color32);
return obj->_color32;
}
static bool color32__new__(int argc, py_Ref argv) {
PY_CHECK_ARGC(5);
c11_color32 color;
for(int i = 1; i < 5; i++) {
PY_CHECK_ARG_TYPE(i, tp_int);
py_i64 val = py_toint(&argv[i]);
if(val < 0 || val > 255) return ValueError("color32 values must be between 0 and 255");
color.data[i - 1] = (unsigned char)val;
}
py_newcolor32(py_retval(), color);
return true;
}
#define DEFINE_COLOR32_FIELD(name) \
static bool color32__##name(int argc, py_Ref argv) { \
PY_CHECK_ARGC(1); \
c11_color32 color = py_tocolor32(argv); \
py_newint(py_retval(), color.name); \
return true; \
} \
static bool color32_with_##name(int argc, py_Ref argv) { \
PY_CHECK_ARGC(2); \
c11_color32 color = py_tocolor32(argv); \
PY_CHECK_ARG_TYPE(1, tp_int); \
py_i64 val = py_toint(&argv[1]); \
if(val < 0 || val > 255) { \
return ValueError("color32 values must be between 0 and 255"); \
} \
color.name = (unsigned char)val; \
py_newcolor32(py_retval(), color); \
return true; \
}
DEFINE_COLOR32_FIELD(r)
DEFINE_COLOR32_FIELD(g)
DEFINE_COLOR32_FIELD(b)
DEFINE_COLOR32_FIELD(a)
#undef DEFINE_COLOR32_FIELD
static bool color32_from_hex_STATIC(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
PY_CHECK_ARG_TYPE(0, tp_str);
c11_sv hex = py_tosv(argv);
c11_color32 color;
int res;
if(hex.size == 7) {
res = sscanf(hex.data, "#%2hhx%2hhx%2hhx", &color.r, &color.g, &color.b);
if(res != 3) return ValueError("invalid hex color format");
color.a = 255;
} else {
res = sscanf(hex.data, "#%2hhx%2hhx%2hhx%2hhx", &color.r, &color.g, &color.b, &color.a);
if(res != 4) return ValueError("invalid hex color format");
}
py_newcolor32(py_retval(), color);
return true;
}
static bool color32_from_vec3_STATIC(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
PY_CHECK_ARG_TYPE(0, tp_vec3);
c11_vec3 v = py_tovec3(argv);
c11_color32 color;
color.r = (unsigned char)(v.x * 255);
color.g = (unsigned char)(v.y * 255);
color.b = (unsigned char)(v.z * 255);
color.a = 255;
py_newcolor32(py_retval(), color);
return true;
}
static bool color32_from_vec3i_STATIC(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
PY_CHECK_ARG_TYPE(0, tp_vec3i);
c11_vec3i v = py_tovec3i(argv);
c11_color32 color;
color.r = (unsigned char)v.x;
color.g = (unsigned char)v.y;
color.b = (unsigned char)v.z;
color.a = 255;
py_newcolor32(py_retval(), color);
return true;
}
static bool color32_to_hex(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_color32 color = py_tocolor32(argv);
char buf[16];
int size;
if(color.a == 255) {
size = snprintf(buf, sizeof(buf), "#%02x%02x%02x", color.r, color.g, color.b);
} else {
size = snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", color.r, color.g, color.b, color.a);
}
py_newstrv(py_retval(), (c11_sv){buf, size});
return true;
}
static void c11_color32_premult(c11_color32* color) {
if(color->a == 255) return;
float alpha = color->a / 255.0f;
color->r = (unsigned char)(color->r * alpha);
color->g = (unsigned char)(color->g * alpha);
color->b = (unsigned char)(color->b * alpha);
}
static bool color32_to_vec3(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_color32 color = py_tocolor32(argv);
c11_color32_premult(&color);
c11_vec3 v;
v.x = (float)color.r / 255;
v.y = (float)color.g / 255;
v.z = (float)color.b / 255;
py_newvec3(py_retval(), v);
return true;
}
static bool color32_to_vec3i(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_color32 color = py_tocolor32(argv);
c11_color32_premult(&color);
c11_vec3i v;
v.x = (int)color.r;
v.y = (int)color.g;
v.z = (int)color.b;
py_newvec3i(py_retval(), v);
return true;
}
static bool color32__eq__(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
if(argv[1].type != tp_color32) {
py_newnotimplemented(py_retval());
return true;
}
c11_color32 lhs = py_tocolor32(&argv[0]);
c11_color32 rhs = py_tocolor32(&argv[1]);
bool eq = memcmp(&lhs, &rhs, sizeof(c11_color32)) == 0;
py_newbool(py_retval(), eq);
return true;
}
static bool color32__ne__(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
if(argv[1].type != tp_color32) {
py_newnotimplemented(py_retval());
return true;
}
c11_color32 lhs = py_tocolor32(&argv[0]);
c11_color32 rhs = py_tocolor32(&argv[1]);
bool eq = memcmp(&lhs, &rhs, sizeof(c11_color32)) != 0;
py_newbool(py_retval(), eq);
return true;
}
static bool color32__repr__(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_color32 color = py_tocolor32(argv);
char buf[64];
int size = snprintf(buf, 64, "color32(%d, %d, %d, %d)", color.r, color.g, color.b, color.a);
py_newstrv(py_retval(), (c11_sv){buf, size});
return true;
}
static bool color32_ansi_fg(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
c11_color32 color = py_tocolor32(argv);
c11_color32_premult(&color);
PY_CHECK_ARG_TYPE(1, tp_str);
c11_sv text = py_tosv(&argv[1]);
c11_sbuf buf;
c11_sbuf__ctor(&buf);
pk_sprintf(&buf, "\x1b[38;2;%d;%d;%dm%v\x1b[0m", color.r, color.g, color.b, text);
c11_sbuf__py_submit(&buf, py_retval());
return true;
}
static bool color32_ansi_bg(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
c11_color32 color = py_tocolor32(argv);
c11_color32_premult(&color);
PY_CHECK_ARG_TYPE(1, tp_str);
c11_sv text = py_tosv(&argv[1]);
c11_sbuf buf;
c11_sbuf__ctor(&buf);
pk_sprintf(&buf, "\x1b[48;2;%d;%d;%dm%v\x1b[0m", color.r, color.g, color.b, text);
c11_sbuf__py_submit(&buf, py_retval());
return true;
}
static bool vmath_rgb(int argc, py_Ref argv) {
PY_CHECK_ARGC(3);
PY_CHECK_ARG_TYPE(0, tp_int);
PY_CHECK_ARG_TYPE(1, tp_int);
PY_CHECK_ARG_TYPE(2, tp_int);
c11_color32 color;
color.r = (unsigned char)py_toint(&argv[0]);
color.g = (unsigned char)py_toint(&argv[1]);
color.b = (unsigned char)py_toint(&argv[2]);
color.a = 255;
py_newcolor32(py_retval(), color);
return true;
}
static bool vmath_rgba(int argc, py_Ref argv) {
PY_CHECK_ARGC(4);
PY_CHECK_ARG_TYPE(0, tp_int);
PY_CHECK_ARG_TYPE(1, tp_int);
PY_CHECK_ARG_TYPE(2, tp_int);
PY_CHECK_ARG_TYPE(3, tp_float);
c11_color32 color;
color.r = (unsigned char)py_toint(&argv[0]);
color.g = (unsigned char)py_toint(&argv[1]);
color.b = (unsigned char)py_toint(&argv[2]);
color.a = (unsigned char)(py_tofloat(&argv[3]) * 255);
py_newcolor32(py_retval(), color);
return true;
}
static bool color32_alpha_blend_STATIC(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
if(!py_checktype(&argv[0], tp_color32)) return false;
if(py_isnone(&argv[1])) {
py_assign(py_retval(), &argv[0]);
return true;
}
if(!py_checktype(&argv[1], tp_color32)) return false;
c11_color32 src = py_tocolor32(&argv[0]);
c11_color32 dst = py_tocolor32(&argv[1]);
float alpha = src.a / 255.0f;
c11_color32 res;
res.r = (unsigned char)(src.r * alpha + dst.r * (1 - alpha));
res.g = (unsigned char)(src.g * alpha + dst.g * (1 - alpha));
res.b = (unsigned char)(src.b * alpha + dst.b * (1 - alpha));
res.a = (unsigned char)(src.a * alpha + dst.a * (1 - alpha));
py_newcolor32(py_retval(), res);
return true;
}
void pk__add_module_vmath() {
py_Ref mod = py_newmodule("vmath");
py_Type vec2 = pk_newtype("vec2", tp_object, mod, NULL, false, true); py_Type vec2 = pk_newtype("vec2", tp_object, mod, NULL, false, true);
py_Type vec3 = pk_newtype("vec3", tp_object, mod, NULL, false, true); py_Type vec3 = pk_newtype("vec3", tp_object, mod, NULL, false, true);
py_Type vec2i = pk_newtype("vec2i", tp_object, mod, NULL, false, true); py_Type vec2i = pk_newtype("vec2i", tp_object, mod, NULL, false, true);
py_Type vec3i = pk_newtype("vec3i", tp_object, mod, NULL, false, true); py_Type vec3i = pk_newtype("vec3i", tp_object, mod, NULL, false, true);
py_Type mat3x3 = pk_newtype("mat3x3", tp_object, mod, NULL, false, true); py_Type mat3x3 = pk_newtype("mat3x3", tp_object, mod, NULL, false, true);
py_Type color32 = pk_newtype("color32", tp_object, mod, NULL, false, true);
py_setdict(mod, py_name("vec2"), py_tpobject(vec2)); py_setdict(mod, py_name("vec2"), py_tpobject(vec2));
py_setdict(mod, py_name("vec3"), py_tpobject(vec3)); py_setdict(mod, py_name("vec3"), py_tpobject(vec3));
py_setdict(mod, py_name("vec2i"), py_tpobject(vec2i)); py_setdict(mod, py_name("vec2i"), py_tpobject(vec2i));
py_setdict(mod, py_name("vec3i"), py_tpobject(vec3i)); py_setdict(mod, py_name("vec3i"), py_tpobject(vec3i));
py_setdict(mod, py_name("mat3x3"), py_tpobject(mat3x3)); py_setdict(mod, py_name("mat3x3"), py_tpobject(mat3x3));
py_setdict(mod, py_name("color32"), py_tpobject(color32));
assert(vec2 == tp_vec2); assert(vec2 == tp_vec2);
assert(vec3 == tp_vec3); assert(vec3 == tp_vec3);
assert(vec2i == tp_vec2i); assert(vec2i == tp_vec2i);
assert(vec3i == tp_vec3i); assert(vec3i == tp_vec3i);
assert(mat3x3 == tp_mat3x3); assert(mat3x3 == tp_mat3x3);
assert(color32 == tp_color32);
/* vec2 */ /* vec2 */
py_bindmagic(vec2, __new__, vec2__new__); py_bindmagic(vec2, __new__, vec2__new__);
@ -997,6 +1253,31 @@ void pk__add_module_linalg() {
(c11_vec3){ (c11_vec3){
{1, 1, 1} {1, 1, 1}
}); });
/* color32 */
py_bindmagic(color32, __new__, color32__new__);
py_bindmagic(color32, __repr__, color32__repr__);
py_bindmagic(color32, __eq__, color32__eq__);
py_bindmagic(color32, __ne__, color32__ne__);
py_bindproperty(color32, "r", color32__r, NULL);
py_bindproperty(color32, "g", color32__g, NULL);
py_bindproperty(color32, "b", color32__b, NULL);
py_bindproperty(color32, "a", color32__a, NULL);
py_bindmethod(color32, "with_r", color32_with_r);
py_bindmethod(color32, "with_g", color32_with_g);
py_bindmethod(color32, "with_b", color32_with_b);
py_bindmethod(color32, "with_a", color32_with_a);
py_bindstaticmethod(color32, "from_hex", color32_from_hex_STATIC);
py_bindstaticmethod(color32, "from_vec3", color32_from_vec3_STATIC);
py_bindstaticmethod(color32, "from_vec3i", color32_from_vec3i_STATIC);
py_bindmethod(color32, "to_hex", color32_to_hex);
py_bindmethod(color32, "to_vec3", color32_to_vec3);
py_bindmethod(color32, "to_vec3i", color32_to_vec3i);
py_bindmethod(color32, "ansi_fg", color32_ansi_fg);
py_bindmethod(color32, "ansi_bg", color32_ansi_bg);
py_bindfunc(mod, "rgb", vmath_rgb);
py_bindfunc(mod, "rgba", vmath_rgba);
py_bindstaticmethod(color32, "alpha_blend", color32_alpha_blend_STATIC);
} }
#undef DEFINE_VEC_FIELD #undef DEFINE_VEC_FIELD

View File

@ -88,7 +88,7 @@ assert list(range(5, 1, -2)) == [5, 3]
# test sort # test sort
a = [8, 2, 4, 2, 9] a = [8, 2, 4, 2, 9]
assert a.sort() == None assert a.sort() == None
assert a == [2, 2, 4, 8, 9] assert (a == [2, 2, 4, 8, 9]), a
a = [] a = []
assert a.sort() == None assert a.sort() == None

View File

@ -87,4 +87,7 @@ class A:
self.b = b self.b = b
a = A(1, ['2', False, None]) a = A(1, ['2', False, None])
assert json.dumps(a.__dict__) == '{"a": 1, "b": ["2", false, null]}' assert json.dumps(a.__dict__) in [
'{"a": 1, "b": ["2", false, null]}',
'{"b": ["2", false, null], "a": 1}',
]

36
tests/80_color32.py Normal file
View File

@ -0,0 +1,36 @@
from vmath import color32, rgb, rgba, vec3i
a = color32(100, 200, 255, 120)
assert a.r == 100
assert a.g == 200
assert a.b == 255
assert a.a == 120
assert a.with_r(255).r == 255
assert a.with_g(255).g == 255
assert a.with_b(255).b == 255
assert a.with_a(255).a == 255 and a.with_a(255).g == a.g
assert a.to_hex() == '#64c8ff78'
assert color32.from_hex('#64c8ff78') == a
assert color32.from_hex('#64c8ff') == a.with_a(255)
assert rgb(100, 200, 255) != a
assert rgba(100, 200, 255, 120 / 255) == a
b = color32(75, 150, 200, 200)
assert a == a and b == b
assert a != b
assert repr(b) == 'color32(75, 150, 200, 200)'
assert color32.from_vec3i(vec3i(100, 200, 255)) == rgb(100, 200, 255)
alpha = a.a / 255
assert a.to_vec3i() == vec3i(int(a.r * alpha), int(a.g * alpha), int(a.b * alpha))
# assert color32.alpha_blend(a, b) == color32(86, 173, 225, 162)
c = rgb(100, 200, 255)
assert c.ansi_fg('xxx') == '\x1b[38;2;100;200;255mxxx\x1b[0m'
assert c.ansi_bg('xxx') == '\x1b[48;2;100;200;255mxxx\x1b[0m'
assert color32.alpha_blend(a, None) == a

View File

@ -1,4 +1,4 @@
from linalg import mat3x3, vec2, vec3, vec2i, vec3i from vmath import mat3x3, vec2, vec3, vec2i, vec3i
import random import random
import math import math

View File

@ -1,5 +1,5 @@
from array2d import array2d from array2d import array2d
from linalg import vec2i from vmath import vec2i
def exit_on_error(): def exit_on_error():
raise KeyboardInterrupt raise KeyboardInterrupt

View File

@ -1,5 +1,5 @@
import array2d import array2d
from linalg import vec2i from vmath import vec2i
def on_builder(a:vec2i): def on_builder(a:vec2i):

View File

@ -1,5 +1,5 @@
import colorcvt import colorcvt
from linalg import vec3 from vmath import vec3
def oklch(expr: str) -> vec3: def oklch(expr: str) -> vec3:
# oklch(82.33% 0.37 153) # oklch(82.33% 0.37 153)

View File

@ -22,7 +22,7 @@ test(False) # PKL_FALSE
test("hello") # PKL_STRING test("hello") # PKL_STRING
test(b"hello") # PKL_BYTES test(b"hello") # PKL_BYTES
from linalg import vec2, vec3, vec2i, vec3i from vmath import vec2, vec3, vec2i, vec3i
test(vec2(2/3, 1.0)) # PKL_VEC2 test(vec2(2/3, 1.0)) # PKL_VEC2
test(vec3(2/3, 1.0, 3.0)) # PKL_VEC3 test(vec3(2/3, 1.0, 3.0)) # PKL_VEC3
@ -186,7 +186,7 @@ class Foo:
test(Foo(1, 2)) test(Foo(1, 2))
test(Foo([1, True], 'c')) test(Foo([1, True], 'c'))
from linalg import vec2 from vmath import vec2
test(vec2(1, 2)) test(vec2(1, 2))

View File

@ -143,3 +143,9 @@ class A:
x: list[int] = [i for i in range(1, 4)] x: list[int] = [i for i in range(1, 4)]
assert A.x == [1, 2, 3] assert A.x == [1, 2, 3]
# stable sort
a = [(0, 1), (1, 1), (1, 2)]
b = sorted(a, key=lambda x: x[0])
if b != [(0, 1), (1, 1), (1, 2)]:
assert False, b

View File

@ -5,7 +5,7 @@ thread_1 = ComputeThread(1)
thread_2 = ComputeThread(2) thread_2 = ComputeThread(2)
for t in [thread_1, thread_2]: for t in [thread_1, thread_2]:
t.exec_blocked(''' t.exec('''
def func(a): def func(a):
from pkpy import currentvm from pkpy import currentvm
print("Hello from thread", currentvm(), "a =", a) print("Hello from thread", currentvm(), "a =", a)
@ -16,13 +16,13 @@ def func(a):
x = 123 x = 123
''') ''')
assert thread_1.eval_blocked('x') == 123 assert thread_1.eval('x') == 123
# thread_1.wait_for_done() # thread_1.wait_for_done()
# thread_2.wait_for_done() # thread_2.wait_for_done()
thread_1.call('func', [1, 2, 3]) thread_1.submit_call('func', [1, 2, 3])
thread_2.call('func', [4, 5, 6]) thread_2.submit_call('func', [4, 5, 6])
while not thread_1.is_done or not thread_2.is_done: while not thread_1.is_done or not thread_2.is_done:
print("Waiting for threads to finish...") print("Waiting for threads to finish...")