From 440acd82f8181964d0efaa6500eaeef5b6988b3a Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 23 Feb 2026 00:35:00 +0800 Subject: [PATCH] refactor `chunked_array2d` --- include/pocketpy/interpreter/array2d.h | 2 +- include/typings/array2d.pyi | 9 +-- src/modules/array2d.c | 87 ++++++++------------------ tests/901_array2d_extra1.py | 9 --- tests/902_chunked_array2d.py | 64 +++---------------- 5 files changed, 37 insertions(+), 134 deletions(-) diff --git a/include/pocketpy/interpreter/array2d.h b/include/pocketpy/interpreter/array2d.h index 1357b04d..7fc49798 100644 --- a/include/pocketpy/interpreter/array2d.h +++ b/include/pocketpy/interpreter/array2d.h @@ -51,7 +51,7 @@ typedef struct c11_chunked_array2d { c11_chunked_array2d_chunks_KV last_visited; py_TValue default_T; - py_TValue context_builder; + bool auto_add_chunk; } c11_chunked_array2d; py_Ref c11_chunked_array2d__get(c11_chunked_array2d* self, int col, int row); diff --git a/include/typings/array2d.pyi b/include/typings/array2d.pyi index 2644adac..bc667678 100644 --- a/include/typings/array2d.pyi +++ b/include/typings/array2d.pyi @@ -136,19 +136,14 @@ class chunked_array2d[T, TContext]: cls, chunk_size: int, default: T | None = None, - context_builder: Callable[[vec2i], TContext] | None = None, + auto_add_chunk: bool = True, ): ... @property def chunk_size(self) -> int: ... - @property - def default(self) -> T: ... - @property - def context_builder(self) -> Callable[[vec2i], TContext] | None: ... def __getitem__(self, index: vec2i) -> T: ... def __setitem__(self, index: vec2i, value: T): ... - def __delitem__(self, index: vec2i): ... def __iter__(self) -> Iterator[tuple[vec2i, TContext]]: ... def __len__(self) -> int: ... @@ -158,7 +153,7 @@ class chunked_array2d[T, TContext]: def world_to_chunk(self, world_pos: vec2i) -> tuple[vec2i, vec2i]: """Converts world position to chunk position and local position.""" - def add_chunk(self, chunk_pos: vec2i) -> TContext: ... + def add_chunk(self, chunk_pos: vec2i, context: TContext | None) -> None: ... def remove_chunk(self, chunk_pos: vec2i) -> bool: ... def move_chunk(self, src_chunk_pos: vec2i, dst_chunk_pos: vec2i) -> bool: ... def get_context(self, chunk_pos: vec2i) -> TContext | None: ... diff --git a/src/modules/array2d.c b/src/modules/array2d.c index 09c5cfaf..9387b7ec 100644 --- a/src/modules/array2d.c +++ b/src/modules/array2d.c @@ -1002,28 +1002,23 @@ static void register_array2d_view(py_Ref mod) { #include "pocketpy/xmacros/smallmap.h" #undef SMALLMAP_T__SOURCE -static py_TValue* c11_chunked_array2d__new_chunk(c11_chunked_array2d* self, c11_vec2i pos) { -#ifndef NDEBUG +static py_TValue* c11_chunked_array2d__new_chunk(c11_chunked_array2d* self, c11_vec2i pos, py_Ref context) { bool exists = c11_chunked_array2d_chunks__contains(&self->chunks, pos); - assert(!exists); -#endif + if(exists) { + ValueError("chunk already exists at pos (%d, %d)", pos.x, pos.y); + return NULL; + } int chunk_numel = self->chunk_size * self->chunk_size + 1; py_TValue* data = PK_MALLOC(sizeof(py_TValue) * chunk_numel); - if(!py_isnone(&self->context_builder)) { - py_newvec2i(&data[0], pos); - bool ok = py_call(&self->context_builder, 1, &data[0]); - if(!ok) { - PK_FREE(data); - return NULL; - } - data[0] = *py_retval(); - } else { - data[0] = *py_None(); - } + data[0] = *context; memset(&data[1], 0, sizeof(py_TValue) * (chunk_numel - 1)); c11_chunked_array2d_chunks__set(&self->chunks, pos, data); self->last_visited.key = pos; self->last_visited.value = data; + // init data with default value + for(int i = 1; i < chunk_numel; i++) { + data[i] = self->default_T; + } return data; } @@ -1077,37 +1072,34 @@ static py_TValue* c11_chunked_array2d__parse_col_row(c11_chunked_array2d* self, py_Ref c11_chunked_array2d__get(c11_chunked_array2d* self, int col, int row) { c11_vec2i chunk_pos, local_pos; py_TValue* data = c11_chunked_array2d__parse_col_row(self, col, row, &chunk_pos, &local_pos); - if(data == NULL) return &self->default_T; - py_Ref retval = &data[1 + local_pos.y * self->chunk_size + local_pos.x]; - if(py_isnil(retval)) return &self->default_T; - return retval; + if(data == NULL) return NULL; + return &data[1 + local_pos.y * self->chunk_size + local_pos.x]; } bool c11_chunked_array2d__set(c11_chunked_array2d* self, int col, int row, py_Ref value) { c11_vec2i chunk_pos, local_pos; py_TValue* data = c11_chunked_array2d__parse_col_row(self, col, row, &chunk_pos, &local_pos); if(data == NULL) { - data = c11_chunked_array2d__new_chunk(self, chunk_pos); - if(data == NULL) return false; + if(self->auto_add_chunk) { + data = c11_chunked_array2d__new_chunk(self, chunk_pos, py_None()); + if(data == NULL) return false; + } else { + return IndexError("(%d, %d) is out of bounds and !auto_add_chunk", col, row); + } } data[1 + local_pos.y * self->chunk_size + local_pos.x] = *value; return true; } -static void c11_chunked_array2d__del(c11_chunked_array2d* self, int col, int row) { - c11_vec2i chunk_pos, local_pos; - py_TValue* data = c11_chunked_array2d__parse_col_row(self, col, row, &chunk_pos, &local_pos); - if(data != NULL) data[1 + local_pos.y * self->chunk_size + local_pos.x] = *py_NIL(); -} - static bool chunked_array2d__new__(int argc, py_Ref argv) { PY_CHECK_ARGC(4); PY_CHECK_ARG_TYPE(1, tp_int); + PY_CHECK_ARG_TYPE(3, tp_bool); py_Type cls = py_totype(argv); c11_chunked_array2d* self = py_newobject(py_retval(), cls, 0, sizeof(c11_chunked_array2d)); int chunk_size = py_toint(&argv[1]); self->default_T = argv[2]; - self->context_builder = argv[3]; + self->auto_add_chunk = py_tobool(&argv[3]); c11_chunked_array2d_chunks__ctor(&self->chunks); self->chunk_size = chunk_size; switch(chunk_size) { @@ -1137,26 +1129,13 @@ static bool chunked_array2d_chunk_size(int argc, py_Ref argv) { return true; } -static bool chunked_array2d_default(int argc, py_Ref argv) { - PY_CHECK_ARGC(1); - c11_chunked_array2d* self = py_touserdata(argv); - py_assign(py_retval(), &self->default_T); - return true; -} - -static bool chunked_array2d_context_builder(int argc, py_Ref argv) { - PY_CHECK_ARGC(1); - c11_chunked_array2d* self = py_touserdata(argv); - py_assign(py_retval(), &self->context_builder); - return true; -} - static bool chunked_array2d__getitem__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); PY_CHECK_ARG_TYPE(1, tp_vec2i); c11_chunked_array2d* self = py_touserdata(argv); c11_vec2i pos = py_tovec2i(&argv[1]); py_Ref res = c11_chunked_array2d__get(self, pos.x, pos.y); + if(res == NULL) return IndexError("(%d, %d) is out of bounds", pos.x, pos.y); py_assign(py_retval(), res); return true; } @@ -1172,16 +1151,6 @@ static bool chunked_array2d__setitem__(int argc, py_Ref argv) { return true; } -static bool chunked_array2d__delitem__(int argc, py_Ref argv) { - PY_CHECK_ARGC(2); - PY_CHECK_ARG_TYPE(1, tp_vec2i); - c11_chunked_array2d* self = py_touserdata(argv); - c11_vec2i pos = py_tovec2i(&argv[1]); - c11_chunked_array2d__del(self, pos.x, pos.y); - py_newnone(py_retval()); - return true; -} - static bool chunked_array2d__iter__(int argc, py_Ref argv) { PY_CHECK_ARGC(1); c11_chunked_array2d* self = py_touserdata(argv); @@ -1258,13 +1227,13 @@ static bool chunked_array2d_world_to_chunk(int argc, py_Ref argv) { } static bool chunked_array2d_add_chunk(int argc, py_Ref argv) { - PY_CHECK_ARGC(2); + PY_CHECK_ARGC(3); PY_CHECK_ARG_TYPE(1, tp_vec2i); c11_chunked_array2d* self = py_touserdata(argv); c11_vec2i pos = py_tovec2i(&argv[1]); - py_TValue* data = c11_chunked_array2d__new_chunk(self, pos); + py_TValue* data = c11_chunked_array2d__new_chunk(self, pos, &argv[2]); if(data == NULL) return false; - py_assign(py_retval(), &data[0]); // context + py_newnone(py_retval()); return true; } @@ -1313,7 +1282,7 @@ static bool chunked_array2d_get_context(int argc, py_Ref argv) { c11_vec2i pos = py_tovec2i(&argv[1]); py_TValue* data = c11_chunked_array2d_chunks__get(&self->chunks, pos, NULL); if(data == NULL) { - py_newnone(py_retval()); + return IndexError("no chunk found at (%d, %d)", pos.x, pos.y); } else { py_assign(py_retval(), &data[0]); } @@ -1328,7 +1297,6 @@ void c11_chunked_array2d__dtor(c11_chunked_array2d* self) { void c11_chunked_array2d__mark(void* ud, c11_vector* p_stack) { c11_chunked_array2d* self = ud; pk__mark_value(&self->default_T); - pk__mark_value(&self->context_builder); int chunk_numel = self->chunk_size * self->chunk_size + 1; for(int i = 0; i < self->chunks.length; i++) { py_TValue* data = c11__getitem(c11_chunked_array2d_chunks_KV, &self->chunks, i).value; @@ -1408,16 +1376,13 @@ static void register_chunked_array2d(py_Ref mod) { assert(type == tp_chunked_array2d); py_bind(py_tpobject(type), - "__new__(cls, chunk_size, default=None, context_builder=None)", + "__new__(cls, chunk_size, default=None, auto_add_chunk=True)", chunked_array2d__new__); py_bindproperty(type, "chunk_size", chunked_array2d_chunk_size, NULL); - py_bindproperty(type, "default", chunked_array2d_default, NULL); - py_bindproperty(type, "context_builder", chunked_array2d_context_builder, NULL); py_bindmagic(type, __getitem__, chunked_array2d__getitem__); py_bindmagic(type, __setitem__, chunked_array2d__setitem__); - py_bindmagic(type, __delitem__, chunked_array2d__delitem__); py_bindmagic(type, __iter__, chunked_array2d__iter__); py_bindmagic(type, __len__, chunked_array2d__len__); diff --git a/tests/901_array2d_extra1.py b/tests/901_array2d_extra1.py index b5b42e6e..570ca079 100644 --- a/tests/901_array2d_extra1.py +++ b/tests/901_array2d_extra1.py @@ -93,15 +93,6 @@ assert data.view() == array2d.fromlist([ assert data.view()[vec2i(1,1)-data.view().origin] == data[vec2i(1,1)] assert data.view()[vec2i(3,3)-data.view().origin] == data[vec2i(3,3)] -# ====chunked_array2d__delitem__ -data = chunked_array2d(4) -for i in range(10): - for j in range(10): - data[vec2i(i,j)] = 10 - -del data[vec2i(0,0)] -assert data[vec2i(0,0)] == data.default - # ====chunked_array2d__len__ data = chunked_array2d(4) for i in range(10): diff --git a/tests/902_chunked_array2d.py b/tests/902_chunked_array2d.py index 77a40184..74a35be2 100644 --- a/tests/902_chunked_array2d.py +++ b/tests/902_chunked_array2d.py @@ -2,66 +2,18 @@ import array2d from vmath import vec2i -def on_builder(a:vec2i): - return str(a) - pass - default = 0 -a = array2d.chunked_array2d(16, default,on_builder) +a = array2d.chunked_array2d(16, default, auto_add_chunk=False) assert a.chunk_size == 16 +a.add_chunk(vec2i(1, 1), 5.0) a[vec2i(16, 16)] = 16 -a[vec2i(15, 16)] = 15 +a[vec2i(17, 16)] = 15 assert a[vec2i(16, 16)] == 16 -assert a[vec2i(15, 16)] == 15 -assert a[vec2i(16, 15)] == default +assert a[vec2i(17, 16)] == 15 +assert a[vec2i(17, 20)] == default -a1,a2=a.world_to_chunk(vec2i(15,16)) +a1, _ = a.world_to_chunk(vec2i(16, 16)) -assert a.remove_chunk(a1)== True -assert a[vec2i(15, 16)] == default - -assert a.get_context(vec2i(1,1))==on_builder(vec2i(1,1)) - -assert a.view().tolist()==[ - [16 if i==0 and j==0 else 0 for j in range(16)] for i in range(16) -] -assert a.view_rect(vec2i(15,15),4,4).tolist()==[ - [0,0,0,0], - [0,16,0,0], - [0,0,0,0], - [0,0,0,0] -] -a[vec2i(15, 16)] = 15 -assert a.view_chunk(a1).tolist()==[ - [15 if i==0 and j==15 else 0 for j in range(16)] for i in range(16) -] -a.clear() - -assert a[vec2i(16, 16)] == default -assert a[vec2i(15, 16)] == default -assert a[vec2i(16, 15)] == default - -from typing import Any - -a = array2d.chunked_array2d[int, Any](4, default=0, context_builder=lambda x: 1) -assert a.chunk_size == 4 - -assert a.add_chunk(vec2i(0, 1)) == 1 -assert a.get_context(vec2i(0, 1)) == 1 - -assert a.move_chunk(vec2i(2, 1), vec2i(1, 1)) == False -assert a.move_chunk(vec2i(0, 1), vec2i(1, 1)) == True - -assert a.get_context(vec2i(1, 1)) == 1 -assert a.get_context(vec2i(0, 1)) == None - -b = a.copy() -assert a is not b -assert a.chunk_size == b.chunk_size -assert a.default == b.default -assert a.context_builder == b.context_builder -assert (a.view() == b.view()).all() - -for pos, ctx in a: - assert b.get_context(pos) == ctx +assert a.get_context(vec2i(1,1)) == 5.0 +assert a.remove_chunk(a1)