{"id":32948997,"url":"https://github.com/oriumgames/bevi","last_synced_at":"2025-11-12T20:00:45.624Z","repository":{"id":322169899,"uuid":"1084303524","full_name":"oriumgames/bevi","owner":"oriumgames","description":"bevy inspired ergonomics for ark ecs","archived":false,"fork":false,"pushed_at":"2025-11-11T01:35:12.000Z","size":106,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-11T03:17:27.876Z","etag":null,"topics":["ark","bevy","codegen","component","ecs","entity","entity-component-system","go","golang","scheduler","system"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oriumgames.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-27T13:58:16.000Z","updated_at":"2025-11-11T01:35:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oriumgames/bevi","commit_stats":null,"previous_names":["oriumgames/bevi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oriumgames/bevi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oriumgames%2Fbevi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oriumgames%2Fbevi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oriumgames%2Fbevi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oriumgames%2Fbevi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oriumgames","download_url":"https://codeload.github.com/oriumgames/bevi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oriumgames%2Fbevi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284100246,"owners_count":26947309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-12T02:00:06.336Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ark","bevy","codegen","component","ecs","entity","entity-component-system","go","golang","scheduler","system"],"created_at":"2025-11-12T20:00:35.988Z","updated_at":"2025-11-12T20:00:45.617Z","avatar_url":"https://github.com/oriumgames.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bevi\n\n[Bevy](https://docs.rs/bevy_ecs/latest/bevy_ecs/)-inspired ergonomics for [Ark](https://github.com/mlange-42/ark/) ECS: codegen, staged scheduling, and fast typed events.\n\n- Simple runtime: `App` + staged scheduler\n- Intelligent parallel scheduler with dependency ordering and access conflict detection\n- Per-type, frame-based, high-performance events with cancellation and completion handles\n- Code generator that wires your systems together from doc comments and function signatures\n\n## Installation\n\nAdd the runtime to your module:\n\n```bash\ngo get github.com/oriumgames/bevi@v0.1.1\n```\n\nOptionally install the generator:\n\n```bash\n# As a binary you can call directly (name depends on your shell/OS, shown here via go run)\ngo install github.com/oriumgames/bevi/cmd/gen@v0.1.1\n```\n\nYou can also run the generator without installing:\n\n```bash\n# From inside this repository or when vendored\ngo run ./cmd/gen -root .\n\n# From another module (using the latest published version)\ngo run github.com/oriumgames/bevi/cmd/gen@v0.1.1 -root .\n```\n\n\n## Quick start\n\n1) Define components and annotate your systems:\n```go\ntype Position struct{ X, Y float64 }\ntype Velocity struct{ X, Y float64 }\n\n//bevi:system Startup\nfunc Spawn(mapper *ecs.Map2[Position, Velocity]) {\n    mapper.NewEntity(\u0026Position{X: 0, Y: 0}, \u0026Velocity{X: 1, Y: 0.5})\n}\n\n//bevi:system Update Every=16ms\nfunc Move(q *ecs.Query2[Position, Velocity]) {\n    for q.Next() {\n        p, v := q.Get()\n        p.X += v.X\n        p.Y += v.Y\n    }\n}\n\n//bevi:system Update After={\"Move\"} Every=1s\nfunc PrintCount(q ecs.Query1[Position]) {\n    n := 0\n    for q.Next() {\n        _, n = q.Get(), n+1\n    }\n    fmt.Println(\"entities:\", n)\n}\n```\n\n2) Generate glue code:\n```bash\ngo run github.com/oriumgames/bevi/cmd/gen@v0.1.1 -root . -write\n```\nThis writes `bevi_gen.go` next to your files and creates a function:\n```go\nfunc Systems(app *bevi.App)\n```\nthat registers all your annotated systems.\n\n3) Boot your app:\n```go\nfunc main() {\n    bevi.NewApp().\n        AddSystems(Systems). // from bevi_gen.go\n        Run()\n}\n```\n\nThat’s it. Your app now runs the staged pipeline; systems are ordered, batched for parallelism, throttled by `Every`, and integrated with typed events.\n\n\n## Writing systems\n\nBevi uses a single doc-comment line to declare scheduling metadata:\n\n```go\n//bevi:system \u003cStage\u003e [Key=Value ...]\n```\n\nSupported keys:\n- Stage: one of PreStartup, Startup, PostStartup, PreUpdate, Update, PostUpdate\n- Every: Go duration (e.g., `500ms`, `1s`) to throttle execution\n- Set: string set/group name (used for Before/After targets as well)\n- After: names or set names the system must run after, e.g., `After={\"A\",\"B\",\"physics\"}`\n- Before: names or set names the system must run before\n- Reads: component types read (overrides inference)\n- Writes: component types written (overrides inference)\n- ResReads: resource types read\n- ResWrites: resource types written\n\nThe generator also infers access from parameters:\n\n- `context.Context` -\u003e passed through\n- `*ecs.World` or `ecs.World` -\u003e passed through\n- `*ecs.MapN[T...]` -\u003e component WRITE access on T...\n- `ecs.QueryN[T...]` -\u003e READ access by default, WRITE access if you accept a pointer `*ecs.QueryN[...]` (write intent marker)\n- `*ecs.FilterN[T...]` -\u003e no direct access (it is a builder used to produce queries)\n- `ecs.Resource[T]` -\u003e resource READ by default (see overrides)\n- `bevi.EventWriter[E]` -\u003e event WRITE access for E\n- `bevi.EventReader[E]` -\u003e event READ access for E\n\nThe generator synthesizes helpers once per package (mappers, filters, resources, event readers/writers), wires everything in a single `Systems(app *bevi.App)` function. It does not auto-close queries; only call `Close()` yourself when you exit iteration early.\n\n### Query lifetime\n\n- Fully iterated queries must NOT be closed.\n- If you exit iteration early, you MUST call `Close()` before leaving the loop.\n- If you need to iterate multiple times (e.g., once per event), create a fresh query each time (prefer passing an `ecs.FilterN[...]` and doing `q := filter.Query()` per pass).\n\nFull iteration (no Close):\n```go\nfunc System(q ecs.Query2[A,B]) {\n    for q.Next() {\n        a, b := q.Get()\n        _ = a; _ = b\n    }\n    // do not call q.Close() here\n}\n```\n\nEarly exit (must Close):\n```go\nfunc System(q *ecs.Query2[A,B]) {\n    for q.Next() {\n        a, b := q.Get()\n        if stop(a, b) {\n            q.Close() // required when exiting early\n            break\n        }\n    }\n}\n```\n\nIterate per event (fresh query per pass):\n```go\n//bevi:system Update Writes={A}\nfunc Apply(reader bevi.EventReader[E], flt ecs.Filter1[A]) {\n    reader.ForEach(func(e E) bool {\n        q := flt.Query() // new cursor each time\n        for q.Next() {\n            a := q.Get()\n            // mutate a\n        }\n        // fully iterated -\u003e no Close()\n        return true // continue to next event\n    })\n}\n```\n\n\n### Filter DSL for queries and filters\n\nYou can refine `ecs.FilterN` (and filters used to spawn queries) via extra doc lines:\n\n```go\n//bevi:filter \u003cparamName | Qk | Fk\u003e [+Type | -Type | !exclusive | !register]...\n```\n\n- `+Type` includes a component type\n- `-Type` excludes a component type\n- `!exclusive` applies Ark’s `.Exclusive()`\n- `!register` applies Ark’s `.Register()`\n- Use `Q0`,`Q1` or `F0`,`F1` to refer to positional query/filter parameters if no name is used\n- Qualified types may use import aliases; the generator normalizes them\n\nExample:\n```go\n//bevi:system Update\n//bevi:filter q +pkg.Position -pkg.Hidden !exclusive\nfunc Move(q *ecs.Query2[pkg.Position, pkg.Velocity]) { ... }\n```\n\n\n## Generator CLI\n\n```\nUsage:\n  gen [flags]\n\nFlags:\n  -root string          root directory to scan (module/package root) (default \".\")\n  -write                write generated files (bevi_gen.go); if false, print to stdout (default true)\n  -v                    verbose logging to stderr\n  -pkg string           only process packages whose name contains this substring\n  -include-tests        include _test.go files during scanning\n```\n\nNotes:\n- The generator writes one `bevi_gen.go` per package that has at least one `//bevi:system` function.\n- It skips `bevi_gen.go` itself to avoid feedback loops.\n- You can run the generator at any time; it is deterministic and safe to re-run.\n\n\n## Runtime: App and stages\n\n`bevi.App` orchestrates Ark’s `ecs.World`, the scheduler, and the event bus:\n\n- Stages:\n  - PreStartup, Startup, PostStartup (run once at boot)\n  - PreUpdate, Update, PostUpdate (run every frame)\n- Between stages, the app completes events for frames with no readers and advances the event bus:\n  - `events.CompleteNoReader()` then `events.Advance()`\n\nTypical boot:\n```go\napp := bevi.NewApp().\n    AddSystems(Systems).        // from bevi_gen.go\n    SetDiagnostics(bevi.NewLogDiagnostics(log.Default()))\n\napp.Run() // blocks until SIGINT/SIGTERM\n```\n\nManual registration (without the generator) is also supported:\n```go\nacc := bevi.NewAccess()\nbevi.AccessWrite[MyComponent](\u0026acc)\nmeta := bevi.SystemMeta{\n    Access: acc,\n    After:  []string{\"OtherSystem\"},\n    Every:  250 * time.Millisecond,\n}\napp.AddSystem(bevi.Update, \"MySystem\", meta, func(ctx context.Context, w *ecs.World) {\n    // ...\n})\n```\n\n\n## Scheduler: ordering, conflicts, and parallelism\n\n- Orders systems with a deterministic topological sort using `Before`/`After` constraints.\n  - Targets can be system names or `Set` names (applies to all members of that set).\n- Builds batches of conflict-free systems to run in parallel.\n- Detects access conflicts using precomputed sets and compact bitsets:\n  - Component conflicts: write/read, write/write\n  - Resource conflicts: write/read, write/write\n  - Event conflicts: writer/reader, writer/writer\n- Respects `Every` on each system; execution is gated by a high-resolution timestamp.\n- Uses a bounded worker pool sized to `GOMAXPROCS` and catches panics, reporting them via diagnostics.\n\n\n## Events: fast, typed, frame-based\n\nA `bevi.EventBus` delivers events from writers to readers frame-by-frame:\n\n- Writers:\n  - `Emit(v T)` fire-and-forget\n  - `EmitResult(v T)` returns `EventResult[T]` with completion/cancellation handles\n  - `EmitAndWait(ctx, v T)` convenience, returns whether it was cancelled\n  - `EmitMany([]T)` bulk emit with fewer allocations\n\n- Readers:\n  - `ForEach(func(T) bool)` is the zero-allocation way to iterate events:\n    ```go\n    reader.ForEach(func(ev MyEvent) bool {\n        // optional cancellation\n        reader.Cancel()\n        if reader.IsCancelled() { /* react */ }\n        return true // return false to stop\n    })\n    ```\n  - `Drain()`, `DrainTo(buf)` special cases for batch extraction (when used, writers rely on `CompleteNoReader()` to finalize)\n\n- Results:\n  - `Valid()`, `Cancelled()`\n  - `Wait(ctx)` blocks until the event finished processing by all readers in the frame\n  - `WaitCancelled(ctx)` returns as soon as cancellation is observed, completion, or ctx done\n\n- Frame semantics:\n  - Writers append to the “write” buffer this frame.\n  - After systems run, the app calls `CompleteNoReader()`, then flips buffers via `Advance()`.\n  - Readers iterate the previous frame’s writes.\n\nYou can access the bus directly via `app.Events()`, or pass it in context using `bevi.WithEventBus` and fetch typed readers/writers with `bevi.ReaderFromContext[T]` and `bevi.WriterFromContext[T]`.\n\n\n## Diagnostics\n\nPlug a diagnostics implementation into your app:\n\n```go\ntype Diagnostics interface {\n    SystemStart(name string, stage bevi.Stage)\n    SystemEnd(name string, stage bevi.Stage, err error, duration time.Duration)\n}\n\napp.SetDiagnostics(bevi.NewLogDiagnostics(log.Default()))\n```\n\nBuilt-ins:\n- `NopDiagnostics` – does nothing\n- `NewLogDiagnostics(l interface{ Printf(string, ...any) })` – logs start/end and durations, reports panics as errors\n\n\n## Example\n\nSee `./example/test`. It demonstrates:\n- Components, events, and multiple `//bevi:system` functions\n- Event cancellation and `WaitCancelled`\n- Dependencies and `Every` throttling\n- Generated `bevi_gen.go` registering all systems\n\n\n## Tips and gotchas\n\n- Re-run the generator whenever you add/change `//bevi:system` or `//bevi:filter` lines or when parameter types change.\n- Pointer-marked queries (`*ecs.QueryN[...]`) are treated as WRITE access; non-pointer queries as READ.\n- `Drain()/DrainTo()` don’t register readers; writers will be finalized by `CompleteNoReader()`. Prefer `ForEach()` for normal consumption.\n- If you register systems manually, ensure you correctly describe access in `SystemMeta.Access` to unlock safe parallelism.\n- If multiple packages contain systems, run the generator once; it will emit a `bevi_gen.go` per package. Call `AddSystems` for each package’s `Systems` function.\n- For reliable timing, use `Every` to gate costly systems rather than `time.Sleep` inside the system.\n\n\n## API surface (selected)\n\nRuntime\n- `type App struct`\n  - `NewApp() *App`\n  - `(*App) AddSystem(stage Stage, name string, meta SystemMeta, fn func(context.Context, *ecs.World)) *App`\n  - `(*App) AddSystems(reg func(*App)) *App`\n  - `(*App) SetDiagnostics(d Diagnostics) *App`\n  - `(*App) Run()`\n  - `(*App) World() *ecs.World`\n  - `(*App) Events() *EventBus`\n\nScheduling\n- `type Stage int` with: PreStartup, Startup, PostStartup, PreUpdate, Update, PostUpdate\n- `type AccessMeta struct` + helpers:\n  - `NewAccess() AccessMeta`\n  - `AccessRead[T]`, `AccessWrite[T]`, `AccessResRead[T]`, `AccessResWrite[T]`\n  - `AccessEventRead[E]`, `AccessEventWrite[E]`\n- `type SystemMeta struct { Access AccessMeta; Set string; Before, After []string; Every time.Duration }`\n\nEvents\n- `type EventBus`\n  - `NewEventBus() *EventBus`\n  - `(*EventBus) Advance()`\n  - `(*EventBus) CompleteNoReader()`\n- `WriterFor[T]`, `ReaderFor[T]`\n- `type EventWriter[T]`\n  - `Emit(T)`, `EmitResult(T) EventResult[T]`, `EmitAndWait(ctx, T) bool`, `EmitMany([]T)`\n- `type EventReader[T]`\n  - `ForEach(func(T) bool)`, `Cancel()`, `IsCancelled()`, `Drain() []T`, `DrainTo([]T) int`\n- `type EventResult[T]`\n  - `Valid() bool`, `Cancelled() bool`, `Wait(ctx) bool`, `WaitCancelled(ctx) bool`\n- `WithEventBus(ctx, *EventBus) context.Context`, `EventBusFrom(ctx) *EventBus`\n- `WriterFromContext[T](ctx) EventWriter[T]`, `ReaderFromContext[T](ctx) EventReader[T]`\n\nDiagnostics\n- `type Diagnostics interface`\n  - `SystemStart(name string, stage Stage)`\n  - `SystemEnd(name string, stage Stage, err error, duration time.Duration)`\n- `NopDiagnostics`, `NewLogDiagnostics(logger)`\n\n\n## License\n\nMIT — see `license.md`.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foriumgames%2Fbevi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foriumgames%2Fbevi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foriumgames%2Fbevi/lists"}