diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 944b311..0d3a5ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,11 +39,11 @@ jobs: VMODULES: ${{ github.workspace }}/modules defaults: run: - working-directory: modules/antono2/mem + working-directory: modules/antono2/memory steps: - uses: actions/checkout@v7 with: - path: modules/antono2/mem + path: modules/antono2/memory - uses: prantlf/setup-v-action@v4 with: version: 0.5.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3893c67..7104ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## Unreleased +- Restore `antono2.memory` as the canonical module, import, repository, and + installed-directory identity. - Add a deterministic power-of-two `BuddyAllocator` with checked allocation records, recursive coalescing, occupancy statistics, tests, and an example. - Add an independent bitmap-model stress test for deterministic first-fit range @@ -13,17 +15,15 @@ All notable changes to this project will be documented in this file. including fragmentation and occupancy diagnostics. - Replay an identical bounded allocation trace for first-fit and buddy benchmark comparisons, with success, failure, peak-use, and trace-integrity diagnostics. -- Run the allocator test suite and canonical `antono2.mem` public-import +- Run the allocator test suite and canonical `antono2.memory` public-import examples on Linux, macOS, and Windows in CI. - Add a task-oriented allocator selection guide covering lifetime, release order, and fragmentation tradeoffs. ## 1.0.3 - 2026-09-10 -- Use `antono2.mem` consistently as the VPM package, import, repository, and - installed-directory identity. -- Rename the source module declaration from `memory` to `mem` to match V's - required leaf-directory name. +- Record the superseded short-name packaging experiment performed before VPM + publication. ## 1.0.2 - 2026-09-10 diff --git a/README.md b/README.md index 038de56..acda9cb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Memory management building blocks for V -[![CI](https://github.com/antono2/mem/actions/workflows/ci.yml/badge.svg)](https://github.com/antono2/mem/actions/workflows/ci.yml) +[![CI](https://github.com/antono2/memory/actions/workflows/ci.yml/badge.svg)](https://github.com/antono2/memory/actions/workflows/ci.yml) `memory` provides small, documented, and tested building blocks for reusing objects and managing bounded memory and resource ranges in V. @@ -16,16 +16,16 @@ Windows. ## Install ```sh -v install antono2.mem +v install antono2.memory ``` -Before the VPM entry is published, clone the release into V's canonical nested -module path: +Before the VPM entry is published, clone the repository into V's canonical +nested module path: ```sh mkdir -p ~/.vmodules/antono2 -git clone --branch v1.0.3 https://github.com/antono2/mem \ - ~/.vmodules/antono2/mem +git clone https://github.com/antono2/memory \ + ~/.vmodules/antono2/memory ``` V 0.5.2 cannot infer that nested path from an unregistered Git URL alone. @@ -33,11 +33,11 @@ V 0.5.2 cannot infer that nested path from an unregistered Git URL alone. Then import it using its canonical VPM name: ```v -import antono2.mem +import antono2.memory ``` -The current 1.x release uses `antono2.mem` as its VPM and import name, matching -the distribution repository and installed directory. +The canonical VPM and import name is `antono2.memory`, matching the repository +and installed directory. Projects that still import `generic_pool` should pin the 0.2.0 release until they are ready to update their imports. @@ -64,7 +64,7 @@ Create a pool once, insert values until it reaches its fixed capacity, and use the returned handle for later access or release: ```v -import antono2.mem +import antono2.memory struct Particle { mut: @@ -73,7 +73,7 @@ mut: } fn main() { - mut particles := mem.new_slot_pool[Particle](128) or { panic(err) } + mut particles := memory.new_slot_pool[Particle](128) or { panic(err) } handle := particles.insert(Particle{x: 10, y: 20}) or { panic(err) } mut particle := particles.get_mut(handle) or { panic('stale particle handle') } @@ -108,7 +108,7 @@ top of checked leases. Its factory runs only when no reusable value is available and the fixed capacity has not been reached. ```v -import antono2.mem +import antono2.memory struct Buffer { mut: @@ -126,7 +126,7 @@ fn reset_buffer(buffer Buffer) Buffer { } fn main() { - mut buffers := mem.new_object_pool[Buffer](16, 4, make_buffer, + mut buffers := memory.new_object_pool[Buffer](16, 4, make_buffer, reset_buffer) or { panic(err) } handle := buffers.acquire() or { panic(err) } @@ -152,10 +152,10 @@ owning the resource itself. It uses deterministic first fit, returns checked allocation records, and coalesces adjacent ranges when they are released. ```v -import antono2.mem +import antono2.memory fn main() { - mut block := mem.new_range_allocator(256 * 1024 * 1024) + mut block := memory.new_range_allocator(256 * 1024 * 1024) vertex_memory := block.allocate(48 * 1024, 256) or { panic(err) } println('bind at offset ${vertex_memory.offset}') @@ -183,10 +183,10 @@ lifetime. Individual ranges are not released; `reset()` invalidates all of them at once while preserving the peak-use statistic. ```v -import antono2.mem +import antono2.memory fn main() { - mut frame_arena := mem.new_linear_allocator(4 * 1024 * 1024) + mut frame_arena := memory.new_linear_allocator(4 * 1024 * 1024) vertices := frame_arena.allocate(96 * 1024, 16) or { panic(err) } uniforms := frame_arena.allocate(256, 256) or { panic(err) } @@ -210,10 +210,10 @@ the same order they were created. It is designed for staging buffers, streaming data, and resources whose lifetime follows a GPU submission or producer queue. ```v -import antono2.mem +import antono2.memory fn main() { - mut uploads := mem.new_ring_allocator(64 * 1024 * 1024) + mut uploads := memory.new_ring_allocator(64 * 1024 * 1024) frame_0 := uploads.allocate(4 * 1024, 256) or { panic(err) } frame_1 := uploads.allocate(8 * 1024, 256) or { panic(err) } @@ -241,10 +241,10 @@ size. Allocation splits larger blocks; release recursively coalesces free buddies. ```v -import antono2.mem +import antono2.memory fn main() { - mut pages := mem.new_buddy_allocator(64 * 1024 * 1024, 256) or { panic(err) } + mut pages := memory.new_buddy_allocator(64 * 1024 * 1024, 256) or { panic(err) } allocation := pages.allocate(6000, 4096) or { panic(err) } assert allocation.offset % 4096 == 0 diff --git a/alignment.v b/alignment.v index aaf6e96..697c744 100644 --- a/alignment.v +++ b/alignment.v @@ -1,4 +1,4 @@ -module mem +module memory // align_forward returns the first value at or after value that is divisible by // alignment. Callers validate that alignment is non-zero before using it. diff --git a/benchmarks/main.v b/benchmarks/main.v index d6ce05b..fb26f65 100644 --- a/benchmarks/main.v +++ b/benchmarks/main.v @@ -1,6 +1,6 @@ module main -import antono2.mem +import antono2.memory import os import time @@ -89,8 +89,8 @@ fn (mut trace AllocationTrace) mix_hash(value u64) { } fn benchmark_slot_pool(operations int) BenchmarkResult { - mut pool := mem.new_slot_pool[u64](1024) or { panic(err) } - mut handles := []mem.Handle{cap: 1024} + mut pool := memory.new_slot_pool[u64](1024) or { panic(err) } + mut handles := []memory.Handle{cap: 1024} mut random_source := BenchmarkRandom{ state: 0x5107cafe } @@ -124,10 +124,9 @@ fn reset_benchmark_object(_ u64) u64 { } fn benchmark_object_pool(operations int) BenchmarkResult { - mut pool := mem.new_object_pool[u64](1024, 1024, make_benchmark_object, reset_benchmark_object) or { - panic(err) - } - mut handles := []mem.Handle{cap: 1024} + mut pool := memory.new_object_pool[u64](1024, 1024, make_benchmark_object, + reset_benchmark_object) or { panic(err) } + mut handles := []memory.Handle{cap: 1024} mut random_source := BenchmarkRandom{ state: 0x0b1ec700 } @@ -159,9 +158,9 @@ fn benchmark_object_pool(operations int) BenchmarkResult { } fn benchmark_range_allocator(operations int) BenchmarkResult { - mut allocator := mem.new_range_allocator(1024 * 1024) + mut allocator := memory.new_range_allocator(1024 * 1024) mut trace := new_allocation_trace() - mut allocations := []mem.RangeAllocation{len: allocator_trace_max_live} + mut allocations := []memory.RangeAllocation{len: allocator_trace_max_live} mut allocated := []bool{len: allocator_trace_max_live} mut checksum := u64(0) mut allocation_attempts := 0 @@ -206,9 +205,9 @@ fn benchmark_range_allocator(operations int) BenchmarkResult { } fn benchmark_buddy_allocator(operations int) BenchmarkResult { - mut allocator := mem.new_buddy_allocator(1024 * 1024, 16) or { panic(err) } + mut allocator := memory.new_buddy_allocator(1024 * 1024, 16) or { panic(err) } mut trace := new_allocation_trace() - mut allocations := []mem.BuddyAllocation{len: allocator_trace_max_live} + mut allocations := []memory.BuddyAllocation{len: allocator_trace_max_live} mut allocated := []bool{len: allocator_trace_max_live} mut checksum := u64(0) mut allocation_attempts := 0 @@ -249,7 +248,7 @@ fn benchmark_buddy_allocator(operations int) BenchmarkResult { } fn benchmark_linear_allocator(operations int) BenchmarkResult { - mut allocator := mem.new_linear_allocator(1024 * 1024) + mut allocator := memory.new_linear_allocator(1024 * 1024) mut random_source := BenchmarkRandom{ state: 0x1a2b3c4d } @@ -278,8 +277,8 @@ fn benchmark_linear_allocator(operations int) BenchmarkResult { } fn benchmark_ring_allocator(operations int) BenchmarkResult { - mut allocator := mem.new_ring_allocator(1024 * 1024) - mut active := []mem.RingAllocation{cap: 2048} + mut allocator := memory.new_ring_allocator(1024 * 1024) + mut active := []memory.RingAllocation{cap: 2048} mut first_active := 0 mut random_source := BenchmarkRandom{ state: 0x71f01234 diff --git a/buddy_allocator.v b/buddy_allocator.v index 8567816..50b1bb9 100644 --- a/buddy_allocator.v +++ b/buddy_allocator.v @@ -1,4 +1,4 @@ -module mem +module memory const buddy_node_free = u8(0) const buddy_node_split = u8(1) diff --git a/buddy_allocator_test.v b/buddy_allocator_test.v index d287182..9bc68d5 100644 --- a/buddy_allocator_test.v +++ b/buddy_allocator_test.v @@ -1,4 +1,4 @@ -module mem +module memory fn test_buddy_allocator_validates_configuration() { if _ := new_buddy_allocator(0, 1) { diff --git a/examples/buddy_allocator/main.v b/examples/buddy_allocator/main.v index 8c6b2bd..9f9917b 100644 --- a/examples/buddy_allocator/main.v +++ b/examples/buddy_allocator/main.v @@ -1,9 +1,9 @@ module main -import antono2.mem +import antono2.memory fn main() { - mut pages := mem.new_buddy_allocator(64 * 1024, 256) or { panic(err) } + mut pages := memory.new_buddy_allocator(64 * 1024, 256) or { panic(err) } uniforms := pages.allocate(700, 256) or { panic(err) } texture_staging := pages.allocate(5000, 4096) or { panic(err) } diff --git a/examples/buffer_pool/main.v b/examples/buffer_pool/main.v index f8d1097..655ba24 100644 --- a/examples/buffer_pool/main.v +++ b/examples/buffer_pool/main.v @@ -1,6 +1,6 @@ module main -import antono2.mem +import antono2.memory struct Buffer { mut: @@ -20,7 +20,9 @@ fn reset_buffer(buffer Buffer) Buffer { } fn main() { - mut buffers := mem.new_object_pool[Buffer](16, 4, make_buffer, reset_buffer) or { panic(err) } + mut buffers := memory.new_object_pool[Buffer](16, 4, make_buffer, reset_buffer) or { + panic(err) + } handle := buffers.acquire() or { panic(err) } mut buffer := buffers.get_mut(handle) or { panic('stale buffer handle') } diff --git a/examples/linear_allocator/main.v b/examples/linear_allocator/main.v index 8c6710d..bbb7522 100644 --- a/examples/linear_allocator/main.v +++ b/examples/linear_allocator/main.v @@ -1,10 +1,10 @@ module main -import antono2.mem +import antono2.memory fn main() { mut frame_memory := []u8{len: 1024} - mut arena := mem.new_linear_allocator(u64(frame_memory.len)) + mut arena := memory.new_linear_allocator(u64(frame_memory.len)) for frame in 0 .. 3 { vertices := arena.allocate(300, 16) or { panic(err) } diff --git a/examples/object_pool/main.v b/examples/object_pool/main.v index 48c8f6b..432d734 100644 --- a/examples/object_pool/main.v +++ b/examples/object_pool/main.v @@ -1,6 +1,6 @@ module main -import antono2.mem +import antono2.memory struct MovingActor { name string = 'MovingActor' @@ -20,7 +20,7 @@ fn reset_moving_actor(actor MovingActor) MovingActor { } fn main() { - mut actors := mem.new_object_pool[MovingActor](2, 1, make_moving_actor, reset_moving_actor) or { + mut actors := memory.new_object_pool[MovingActor](2, 1, make_moving_actor, reset_moving_actor) or { panic(err) } diff --git a/examples/range_allocator/main.v b/examples/range_allocator/main.v index fa4d2d0..2e312f8 100644 --- a/examples/range_allocator/main.v +++ b/examples/range_allocator/main.v @@ -1,10 +1,10 @@ module main -import antono2.mem +import antono2.memory fn main() { mut backing_buffer := []u8{len: 256} - mut ranges := mem.new_range_allocator(u64(backing_buffer.len)) + mut ranges := memory.new_range_allocator(u64(backing_buffer.len)) vertices := ranges.allocate(36, 16) or { panic(err) } indices := ranges.allocate(12, 4) or { panic(err) } diff --git a/examples/ring_allocator/main.v b/examples/ring_allocator/main.v index e82e6b9..1e3ef59 100644 --- a/examples/ring_allocator/main.v +++ b/examples/ring_allocator/main.v @@ -1,10 +1,10 @@ module main -import antono2.mem +import antono2.memory fn main() { mut staging_buffer := []u8{len: 64} - mut uploads := mem.new_ring_allocator(u64(staging_buffer.len)) + mut uploads := memory.new_ring_allocator(u64(staging_buffer.len)) frame_0 := uploads.allocate(24, 16) or { panic(err) } frame_1 := uploads.allocate(24, 16) or { panic(err) } diff --git a/linear_allocator.v b/linear_allocator.v index 9e2aa2a..ead03e1 100644 --- a/linear_allocator.v +++ b/linear_allocator.v @@ -1,4 +1,4 @@ -module mem +module memory // LinearAllocation identifies one aligned range from a LinearAllocator. It is // valid until the allocator is reset. diff --git a/linear_allocator_test.v b/linear_allocator_test.v index aa959f9..cad8838 100644 --- a/linear_allocator_test.v +++ b/linear_allocator_test.v @@ -1,4 +1,4 @@ -module mem +module memory fn test_linear_allocator_aligns_and_accounts_for_padding() { mut allocator := new_linear_allocator(64) diff --git a/object_pool.v b/object_pool.v index eb7648d..65d4a8c 100644 --- a/object_pool.v +++ b/object_pool.v @@ -1,4 +1,4 @@ -module mem +module memory // ObjectFactory creates one value when an ObjectPool has no reusable value // available. diff --git a/object_pool_test.v b/object_pool_test.v index 1dedf36..c052a89 100644 --- a/object_pool_test.v +++ b/object_pool_test.v @@ -1,4 +1,4 @@ -module mem +module memory struct ReusableItem { mut: diff --git a/range_allocator.v b/range_allocator.v index c1d0510..dfca981 100644 --- a/range_allocator.v +++ b/range_allocator.v @@ -1,4 +1,4 @@ -module mem +module memory struct FreeRange { mut: diff --git a/range_allocator_test.v b/range_allocator_test.v index 75d0b4f..5f5f896 100644 --- a/range_allocator_test.v +++ b/range_allocator_test.v @@ -1,4 +1,4 @@ -module mem +module memory fn test_range_allocator_aligns_tracks_and_coalesces() { mut allocator := new_range_allocator(128) diff --git a/ring_allocator.v b/ring_allocator.v index 3ef071c..20b4e71 100644 --- a/ring_allocator.v +++ b/ring_allocator.v @@ -1,4 +1,4 @@ -module mem +module memory struct RingRecord { id u64 diff --git a/ring_allocator_test.v b/ring_allocator_test.v index 4290abf..863df82 100644 --- a/ring_allocator_test.v +++ b/ring_allocator_test.v @@ -1,4 +1,4 @@ -module mem +module memory fn test_ring_allocator_aligns_and_accounts_for_padding() { mut allocator := new_ring_allocator(32) diff --git a/scripts/run_benchmarks.sh b/scripts/run_benchmarks.sh index 40a36e0..d4fca39 100755 --- a/scripts/run_benchmarks.sh +++ b/scripts/run_benchmarks.sh @@ -11,6 +11,6 @@ if [ "$operations" = '--quick' ]; then fi mkdir -p "$modules_dir/antono2" -ln -s "$repo_dir" "$modules_dir/antono2/mem" +ln -s "$repo_dir" "$modules_dir/antono2/memory" VMODULES="$modules_dir" v -prod run "$repo_dir/benchmarks" "$operations" diff --git a/scripts/run_examples.sh b/scripts/run_examples.sh index f4d8c63..787913b 100755 --- a/scripts/run_examples.sh +++ b/scripts/run_examples.sh @@ -6,7 +6,7 @@ modules_dir=$(mktemp -d "${TMPDIR:-/tmp}/v-memory-modules.XXXXXX") trap 'rm -rf "$modules_dir"' EXIT HUP INT TERM mkdir -p "$modules_dir/antono2" -ln -s "$repo_dir" "$modules_dir/antono2/mem" +ln -s "$repo_dir" "$modules_dir/antono2/memory" for example in object_pool buffer_pool range_allocator linear_allocator ring_allocator buddy_allocator; do VMODULES="$modules_dir" v run "$repo_dir/examples/$example" diff --git a/slot_pool.v b/slot_pool.v index 8f742dc..1334f9e 100644 --- a/slot_pool.v +++ b/slot_pool.v @@ -1,4 +1,4 @@ -module mem +module memory // Handle identifies one occupied slot at a specific generation. // diff --git a/slot_pool_test.v b/slot_pool_test.v index 7ac600a..2ffcfa7 100644 --- a/slot_pool_test.v +++ b/slot_pool_test.v @@ -1,4 +1,4 @@ -module mem +module memory struct TestItem { mut: diff --git a/v.mod b/v.mod index 35b95fc..4403d76 100644 --- a/v.mod +++ b/v.mod @@ -1,9 +1,9 @@ Module { - name: 'antono2.mem' + name: 'antono2.memory' author: 'Anton Oreskin' description: 'Reusable memory pools and allocation algorithms for V' version: '1.0.3' license: 'MIT' - repo_url: 'https://github.com/antono2/mem' + repo_url: 'https://github.com/antono2/memory' tags: ['V', 'pool', 'allocator', 'memory-management'] }