Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
37144fd
Overload `unsafe_set` to work with pointer to component data array
moseschmiedel Aug 7, 2025
1ee8de5
Test if archetypes have correct entity amount
moseschmiedel Aug 7, 2025
4d06f1c
Debug printing
moseschmiedel Aug 7, 2025
2ef1c17
Add StaticVariant
moseschmiedel Aug 12, 2025
1d52c55
Implement common traits for StaticVariant when wrapped type implement…
moseschmiedel Aug 13, 2025
f490b90
Add batch component removal
moseschmiedel Aug 28, 2025
2374e30
Add test for batch component removal
moseschmiedel Aug 28, 2025
0f0a57c
Change batch addition test to process more entities
moseschmiedel Aug 28, 2025
c9ed480
Change batch addition test to process more entities
moseschmiedel Aug 28, 2025
9f8a7f4
Align docstring for batch `world.remove` to docstring for batch `worl…
moseschmiedel Sep 2, 2025
9eeb6f0
Fix assertions in `test_world_batch_remove` to use entity methods
moseschmiedel Sep 2, 2025
da69882
Move entities during `world.remove` and update `EntityIndex`
moseschmiedel Sep 2, 2025
685f562
Extend batch addition benchmark to cover batch removal
moseschmiedel Sep 3, 2025
5aa1f48
Add 1000x1000 batch add & remove benchmark
moseschmiedel Sep 3, 2025
6b50333
Don't expose internal detail (archetypes) in user-facing error message
moseschmiedel Sep 24, 2025
e1b8c2a
Remove duplicate lines in `world_test.mojo`
moseschmiedel Sep 24, 2025
2f80599
Update changelog
moseschmiedel Sep 24, 2025
b0caee4
v0.4.0 is already released
moseschmiedel Sep 24, 2025
f44687a
Fix import statement and use new DType.uint
moseschmiedel Sep 23, 2025
2e9ff95
Remove `run_destructors` parameter from `InlineArray`
moseschmiedel Sep 23, 2025
792a8c9
Introduce `ImplicitlyCopyable` trait and remove `hint_trivial_type` f…
moseschmiedel Sep 23, 2025
8109f8e
Use 'var' instead of 'owned', `sizeof` renamed to `size_of`
moseschmiedel Sep 23, 2025
b9df1cc
Add `deinit` keyword to `__moveinit__` constructors
moseschmiedel Sep 23, 2025
660a14c
Use explicit `.copy()` more often
moseschmiedel Sep 24, 2025
526176a
Update indexing
moseschmiedel Sep 24, 2025
8a75477
Fix indexing in EntityAccessor and cleanup copy for Node
moseschmiedel Sep 24, 2025
d34ee18
`unsafe_take` is not needed anymore, use `VariadicPack.consume_elemen…
moseschmiedel Sep 24, 2025
2d7236f
`ArchetypeByListIterator` should take an owned List as argument
moseschmiedel Sep 24, 2025
d30bcd7
Update Mojo to 0.25.6
moseschmiedel Sep 24, 2025
f6c57b0
Update benchmark code to Mojo 0.25.6
moseschmiedel Sep 24, 2025
18469f1
Update AoS benchmark Mojo version to 0.25.6
moseschmiedel Sep 24, 2025
701fa2d
`copy` should not be overridden anymore
moseschmiedel Sep 24, 2025
73b13a9
Update examples to Mojo 0.25.6
moseschmiedel Sep 24, 2025
c9ac903
Remove `ImplicitlyCopyable` from `Archetype`
moseschmiedel Sep 24, 2025
533c4dc
Make `Entity` and `EntityIndex` `ImplicitlyCopyable` and make `Entity…
moseschmiedel Sep 24, 2025
b8d4ef0
Make `LockMask` `ImplicitlyCopyable`
moseschmiedel Sep 24, 2025
7bb8450
Make `Query` and `QueryInfo` `ImplicitlyCopyable`
moseschmiedel Sep 24, 2025
3708273
Remove `ImplicitlyCopyable` from `StaticOptional`, make `StaticVarian…
moseschmiedel Sep 24, 2025
28e94c1
Remove `ImplicitlyCopyable` from `ComponentType`
moseschmiedel Sep 24, 2025
3057731
Rename `LockMask` to `LockManager` and remove `ImplicitlyCopyable`
moseschmiedel Sep 24, 2025
b31e208
Remove redundant `Raises:` from doc-string
moseschmiedel Sep 24, 2025
fea661a
Add explicit origin parameter for ref in `test_utils.{load,store}`
moseschmiedel Sep 25, 2025
09edea6
Upgrade Modo version
moseschmiedel Sep 25, 2025
af9c9c8
Update changelog
moseschmiedel Sep 25, 2025
5dfcb99
`Dict.__getitem__` returns mutable ref now
moseschmiedel Sep 25, 2025
cabfd31
Add suggestions to `changelog.md`
moseschmiedel Sep 29, 2025
a1fd4fc
Use implicit Bool cast to check for non-empty `List`
moseschmiedel Sep 29, 2025
e2ba4f8
Remove dead overload for `Resources._add`
moseschmiedel Sep 29, 2025
d5d494d
Copy `without_mask` always implicitly to increase ergonomics
moseschmiedel Sep 29, 2025
b598bb3
Move `start_indices` in `world._get_entity_iterator` as it is not nee…
moseschmiedel Sep 29, 2025
cd2128a
Infer-only bug seems to be fixed
moseschmiedel Sep 29, 2025
06907b1
Take `mask: BitMask` not as owned
moseschmiedel Sep 29, 2025
e6cb6ec
Merge batch `world.{add,remove}` implementations with single helper f…
moseschmiedel Sep 25, 2025
240685d
Add batch replace functionality
moseschmiedel Sep 25, 2025
bf44e16
Add test for batch replace
moseschmiedel Sep 25, 2025
494c54c
Make comment in `_batch_remove_and_add` more generic
moseschmiedel Sep 26, 2025
caa6339
Move old component data to new archetype before setting new component…
moseschmiedel Sep 26, 2025
1bc0120
Change `BitMask.without` to return a `BitMask` again, implement more …
moseschmiedel Sep 26, 2025
a5ada4e
Refine component addition check for replace case
moseschmiedel Sep 26, 2025
11dcf49
Rename `BitMask.without` to `BitMask.set` overload
moseschmiedel Sep 26, 2025
c3431e9
Remove `BitMask.without` benchmark
moseschmiedel Sep 26, 2025
a987abe
Add batch `world.replace` benchmarks
moseschmiedel Sep 26, 2025
e2f9964
Test for specific error message in `world.replace` test
moseschmiedel Sep 26, 2025
0ebe87a
Add missing `prevent_inlining_` functions to `world_benchmark.mojo` a…
moseschmiedel Sep 26, 2025
242e13c
Update documentation for batch `world.remove` and `world.replace`
moseschmiedel Sep 26, 2025
d304368
Udpate changelog
moseschmiedel Sep 26, 2025
bce2cda
Fix `List` construction in `_batch_remove_and_add` to use argument as…
moseschmiedel Sep 29, 2025
04c8cb9
`world._remove_and_add` refactor
moseschmiedel Sep 29, 2025
eb23dee
Add more checks to `world_test.test_remove_and_add()`
moseschmiedel Sep 30, 2025
9db7474
Refactor `BitMask` to not care about filtering components
moseschmiedel Sep 30, 2025
25fd3ce
Remove `QueryInfo` and use `MaskFilter` for component matching logic
moseschmiedel Sep 30, 2025
99f0c18
fixup bitmask
moseschmiedel Sep 30, 2025
84771d1
Refactor usage of `QueryInfo` to use `MaskFilter` instead
moseschmiedel Sep 30, 2025
30632a1
Add CLI arg parser to benchmark framework
moseschmiedel Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:
pages: write

env:
MODO_VERSION: v0.11.10
MODO_VERSION: v0.11.12
HUGO_VERSION: 0.148.2

jobs:
Expand Down
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Agent Guidelines for Larecs

## Build/Test Commands
- Run all tests: `pixi run mojo test -I src/ test/`
- Run single test: `pixi run mojo test -I src/ test/<filename>.mojo`
- Format code: `pixi run mojo format src test benchmark`
- Generate docs: `pixi run mojo doc -o docs/src/larecs.json src/larecs`

## Code Style
- Use snake_case for functions/variables, PascalCase for types
- Use `fn` for static functions, `def` for dynamic functions
- Prefer `var` for mutable, immutable by default
- Use `inout` parameters for mutation, not return modified values
- Include comprehensive type hints with Mojo's progressive typing
- Use `@parameter` for compile-time constants, `@always_inline` for critical paths
- Leverage SIMD types `SIMD[type, width]` for vectorization
- Apply traits: `Copyable`, `Movable`, `Stringable` appropriately
- Use manual memory management with `UnsafePointer` when needed
- Include docstrings for public APIs
- Reference Mojo docs for LLMs: https://docs.modular.com/llms-mojo.txt

## Error Handling & Safety
- Follow borrow checker principles for memory safety
- Prefer stack allocation and RAII patterns
- Use `debug_warn()` utility for debug messages

## Performance Focus
- This is a performance-critical ECS library
- Memory layout and cache efficiency are crucial
- Always consider vectorization opportunities
- Update benchmarks when making performance changes
18 changes: 1 addition & 17 deletions benchmark/bitmask_benchmark.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn benchmark_bitmask_flip_1_000_000(mut bencher: Bencher) capturing:
@parameter
fn bench_fn() capturing:
for _ in range(1_000_000):
mask.flip(val)
mask.flip_mut(val)
keep(mask._bytes)

bencher.iter[bench_fn]()
Expand Down Expand Up @@ -75,19 +75,6 @@ fn benchmark_bitmask_contains_any_1_000_000(mut bencher: Bencher) capturing:
bencher.iter[bench_fn]()


fn benchmark_mask_filter_1_000_000(mut bencher: Bencher) capturing:
mask = BitMask(0, 1, 2).without()
bits = BitMask(0, 1, 2)

@always_inline
@parameter
fn bench_fn() capturing:
for _ in range(1_000_000):
keep(mask.matches(bits))

bencher.iter[bench_fn]()


fn benchmark_bitmask_eq_1_000_000(mut bencher: Bencher) capturing:
mask1 = get_random_bitmask()
mask2 = mask1
Expand Down Expand Up @@ -202,9 +189,6 @@ fn run_all_bitmask_benchmarks(mut bench: Bench) raises:
bench.bench_function[benchmark_bitmask_eq_1_000_000](
BenchId("10^6 * bitmask_eq")
)
bench.bench_function[benchmark_mask_filter_1_000_000](
BenchId("10^6 * mask_filter")
)
# bench.bench_function[benchmark_bitmask_get_indices_1_000_000](
# BenchId("10^6 * get_indices")
# )
Expand Down
188 changes: 187 additions & 1 deletion benchmark/custom_benchmark.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,202 @@ from benchmark import (
BenchId,
BenchConfig as BenchConfig_,
Bench as Bench_,
Format,
)
from pathlib import Path
from time import perf_counter_ns
from collections import Dict
from larecs.bitmask import BitMask


fn DefaultConfig() raises -> BenchConfig_:
"""Returns the default configuration for benchmarking."""
config = BenchConfig_(min_runtime_secs=2, max_batch_size=50)
config.verbose_timing = True
return config
return config^


struct ArgTypes:
alias path = "--path"
alias format = "--format"


struct FormatStrings:
alias csv = "csv"
alias table = "table"
alias tabular = "tabular"

@staticmethod
fn contains(str: StringSlice[StaticConstantOrigin]) -> Bool:
return (
str == FormatStrings.csv
or str == FormatStrings.table
or str == FormatStrings.tabular
)


@fieldwise_init
struct Arg(Copyable, Movable):
var type: StringSlice[StaticConstantOrigin]
var value: List[StringSlice[StaticConstantOrigin]]

fn __eq__(self: Self, other: Arg) -> Bool:
return self.type == other.type

fn __eq__(self: Self, other: StringSlice[StaticConstantOrigin]) -> Bool:
return self.type == other


@register_passable("trivial")
struct ParserError(EqualityComparable):
var type: BitMask.IndexType
alias unknown_arg = ParserError(0)
alias format_missing = ParserError(1)
alias path_missing = ParserError(2)

fn __init__(out self, type: BitMask.IndexType):
self.type = type

fn __eq__(self: Self, other: ParserError) -> Bool:
return self.type == other.type


@fieldwise_init
struct ParserErrors(ImplicitlyCopyable, Movable):
var error_mask: BitMask

fn __init__(out self):
self.error_mask = BitMask()

fn has_errors(self: Self) -> Bool:
return not self.error_mask.is_zero()

fn has_error(self: Self, error: ParserError) -> Bool:
return self.error_mask.get(error.type)

fn add_error(mut self: Self, error: ParserError):
self.error_mask.set[True](error.type)

fn clear_error(mut self: Self, error: ParserError):
self.error_mask.set[False](error.type)

fn get_errors(self: Self) -> List[ParserError]:
errors = List[ParserError]()
for error_bit in self.error_mask.get_indices():
errors.append(ParserError(error_bit))
return errors^


struct Parser:
var args: List[StringSlice[StaticConstantOrigin]]
var index: Int
var errors: ParserErrors

fn __init__(out self, var args: List[StringSlice[StaticConstantOrigin]]):
self.args = args^
self.index = 0
self.errors = ParserErrors()

fn has_next(self: Self) -> Bool:
return self.index < len(self.args)

fn parse_next(mut self: Self) raises -> Arg:
type = self.args[self.index]
self.index += 1

value = List[StringSlice[StaticConstantOrigin]]()
if type == ArgTypes.path:
if not self.has_next():
raise Error("Expected a value after --path")

value.append(self.args[self.index])
self.index += 1

if self.errors.has_error(ParserError.format_missing):
raise Error(
"--path specified without --format. Please provide --format"
" first."
)
elif self.errors.has_error(ParserError.path_missing):
self.errors.clear_error(ParserError.path_missing)
else:
self.errors.add_error(ParserError.format_missing)

elif type == ArgTypes.format:
if not self.has_next():
raise Error("Expected a value after --format")

format_str = self.args[self.index]
if not FormatStrings.contains(format_str):
raise Error("Unknown format: " + format_str)

value.append(format_str)
self.index += 1

# but this arg should only appear if the --path arg is also given
if self.errors.has_error(ParserError.format_missing):
self.errors.clear_error(ParserError.format_missing)
elif self.errors.has_error(ParserError.path_missing):
raise Error(
"--format specified without --path. Please provide --path"
" first."
)
else:
self.errors.add_error(ParserError.path_missing)

else:
raise Error("Unknown argument: " + type)

return Arg(type, value^)

fn parse_all(mut self: Self) raises -> List[Arg]:
parsed_args = List[Arg]()
while self.has_next():
parsed_args.append(self.parse_next())
return parsed_args^


fn config_from_args(
args: VariadicList[StringSlice[StaticConstantOrigin]],
) raises -> BenchConfig_:
"""Parses command line arguments to create a BenchConfig.

Currently supports:
--json : Outputs results in JSON format.

Args:
args: The command line arguments.

Returns:
A BenchConfig with the parsed settings.
"""
config = DefaultConfig()

args_list = List[StringSlice[StaticConstantOrigin]](capacity=len(args))
for arg in args:
args_list.append(arg)

parser = Parser(args_list[1:])
parsed_args = parser.parse_all()

if parser.errors.has_errors():
error_msgs = List[String]()
for error in parser.errors.get_errors():
if error == ParserError.format_missing:
error_msgs.append("--format specified without --path")
elif error == ParserError.path_missing:
error_msgs.append("--path specified without --format")
elif error == ParserError.unknown_arg:
error_msgs.append("Unknown argument")
raise Error("Argument parsing errors:\n" + "\n".join(error_msgs^))

for arg in parsed_args:
if arg == ArgTypes.format:
config.out_file_format = Format(arg.value[0])
elif arg == ArgTypes.path:
config.out_file = Path(arg.value[0])

return config^


fn DefaultBench() raises -> Bench_:
Expand Down
45 changes: 28 additions & 17 deletions benchmark/plots/pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmark/plots/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version = "0.4.0"
[tasks]

[dependencies]
mojo = ">=25.5,<26"
mojo = "==0.25.6"
python = "==3.12"
matplotlib = ">=3.10.0,<4"
pandas = ">=2.2"
Loading
Loading