{"id":27147395,"url":"https://github.com/thomvanoorschot/backstage","last_synced_at":"2025-09-12T00:19:10.213Z","repository":{"id":278444749,"uuid":"932330571","full_name":"Thomvanoorschot/backstage","owner":"Thomvanoorschot","description":"This repository contains an experimental actor framework built using the Zig programming language. The framework implements actor-based concurrent programming patterns, including message passing between actors, actor lifecycle management, and state isolation. It leverages the libxev library for its event loop and concurrency.","archived":false,"fork":false,"pushed_at":"2025-08-01T18:11:17.000Z","size":15063,"stargazers_count":26,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-01T20:36:02.228Z","etag":null,"topics":["actor","actor-framework","libuv","libxev","zig-package","ziglang"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Thomvanoorschot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2025-02-13T18:33:40.000Z","updated_at":"2025-07-28T00:56:10.000Z","dependencies_parsed_at":"2025-03-21T19:28:02.335Z","dependency_job_id":"f1b72994-b7e4-440a-8d50-a84ed6e5d5aa","html_url":"https://github.com/Thomvanoorschot/backstage","commit_stats":null,"previous_names":["thomvanoorschot/alphazig","thomvanoorschot/backstage"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Thomvanoorschot/backstage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thomvanoorschot%2Fbackstage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thomvanoorschot%2Fbackstage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thomvanoorschot%2Fbackstage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thomvanoorschot%2Fbackstage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Thomvanoorschot","download_url":"https://codeload.github.com/Thomvanoorschot/backstage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thomvanoorschot%2Fbackstage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274729351,"owners_count":25338666,"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-09-11T02:00:13.660Z","response_time":74,"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":["actor","actor-framework","libuv","libxev","zig-package","ziglang"],"created_at":"2025-04-08T11:26:02.480Z","updated_at":"2025-09-12T00:19:10.201Z","avatar_url":"https://github.com/Thomvanoorschot.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Backstage: Actor Framework for Zig\n\nBackstage is a high-performance, event-driven actor framework for the Zig programming language. Built on top of [libxev](https://github.com/mitchellh/libxev), it provides a robust foundation for building concurrent applications using the actor model pattern with automatic proxy generation.\n\n## Key Features\n\n- **Proxy-Based Actors**: Automatic code generation creates type-safe proxies for seamless actor communication\n- **Method-Based Communication**: Actors expose public methods with automatic parameter serialization\n- **Stream-Based Pub/Sub**: High-performance streaming system for publish/subscribe patterns\n- **Event-Driven Architecture**: Built on libxev for high-performance, non-blocking I/O operations\n- **Runtime Inspector**: Real-time monitoring and debugging of actor systems with a graphical interface\n- **Type Safety**: Compile-time validation of actor method calls and parameters\n\n## How It Works\n\nBackstage uses code generation to create type-safe proxies for your actors. Simply mark your actor structs with `// @generate-proxy`, define public methods for your business logic, and the framework handles the rest.\n\n### Simple Actor Example\n\n```zig\nconst backstage = @import(\"backstage\");\nconst std = @import(\"std\");\nconst Context = backstage.Context;\nconst HelloWorldActorProxy = @import(\"generated/hello_world_actor_proxy.gen.zig\").HelloWorldActorProxy;\n\n// @generate-proxy\npub const HelloWorldActor = struct {\n    ctx: *Context,\n    allocator: std.mem.Allocator,\n    message_count: u32 = 0,\n\n    const Self = @This();\n\n    pub fn init(ctx: *Context, allocator: std.mem.Allocator) !*Self {\n        const self = try allocator.create(Self);\n        self.* = .{\n            .ctx = ctx,\n            .allocator = allocator,\n        };\n        return self;\n    }\n\n    pub fn sayHello(self: *Self, name: []const u8) !void {\n        self.message_count += 1;\n        std.log.info(\"Hello, {s}! (Message #{d})\", .{ name, self.message_count });\n    }\n\n    pub fn getCount(self: *Self) u32 {\n        return self.message_count;\n    }\n\n    pub fn deinit(_: *Self) !void {}\n};\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    defer _ = gpa.deinit();\n    const allocator = gpa.allocator();\n\n    var engine = try backstage.Engine.init(allocator);\n    defer engine.deinit();\n\n    const actor = try engine.getActor(HelloWorldActorProxy, \"hello_actor\");\n    try actor.sayHello(\"World\");\n    try actor.sayHello(\"Zig\");\n\n    try engine.loop.run(.once);\n}\n```\n\n### Actor-to-Actor Communication\n\n```zig\n// @generate-proxy\npub const SenderActor = struct {\n    ctx: *Context,\n    allocator: std.mem.Allocator,\n\n    pub fn init(ctx: *Context, allocator: std.mem.Allocator) !*Self {\n        // Initialize actor\n    }\n\n    pub fn sendMessage(self: *Self, message: []const u8) !void {\n        const receiver = try self.ctx.getActor(ReceiverActorProxy, \"receiver\");\n        try receiver.processMessage(message);\n    }\n\n    pub fn deinit(_: *Self) !void {}\n};\n\n// @generate-proxy\npub const ReceiverActor = struct {\n    ctx: *Context,\n    allocator: std.mem.Allocator,\n\n    pub fn init(ctx: *Context, allocator: std.mem.Allocator) !*Self {\n        // Initialize actor\n    }\n\n    pub fn processMessage(self: *Self, message: []const u8) !void {\n        std.log.info(\"Received: {s}\", .{message});\n    }\n\n    pub fn deinit(_: *Self) !void {}\n};\n```\n\n### Publish/Subscribe with Streams\n\n```zig\n// Publisher Actor\npub fn publishNews(self: *Self, headline: []const u8) !void {\n    const stream = try self.ctx.getStream([]const u8, \"news\");\n    try stream.next(headline);\n}\n\n// Subscriber Actor\npub fn subscribeToNews(self: *Self) !void {\n    const stream = try self.ctx.getStream([]const u8, \"news\");\n    try stream.subscribe(\n        backstage.newSubscriber(\"subscriber_id\", SubscriberProxy.Method.handleNews)\n    );\n}\n\npub fn handleNews(self: *Self, headline: []const u8) !void {\n    std.log.info(\"Breaking news: {s}\", .{headline});\n}\n```\n\n## Installation\n\nAdd Backstage to your `build.zig.zon`:\n\n```zig\n.dependencies = .{\n    .backstage = .{\n        .url = \"https://github.com/Thomvanoorschot/backstage/archive/main.tar.gz\",\n        .hash = \"...\", // Update with actual hash\n    },\n},\n```\n\n## Setup\n\n### 1. Configure Build System\n\nIn your `build.zig`:\n\n```zig\nconst backstage_dep = b.dependency(\"backstage\", .{\n    .target = target,\n    .optimize = optimize,\n    .generate_proxies = true,\n});\n\n// Set up proxy generation\nconst generator = backstage_dep.artifact(\"generator\");\nconst run_generator = b.addRunArtifact(generator);\nrun_generator.addArg(\"src/generated\"); // Output directory\nrun_generator.addArg(\"src\");           // Source directory to scan\nrun_generator.addArg(\"other_dir\");     // Multiple directories can be scanned by adding args\n\nconst gen_proxies = b.step(\"gen-proxies\", \"Generate actor proxies\");\ngen_proxies.dependOn(\u0026run_generator.step);\nb.getInstallStep().dependOn(gen_proxies);\n\n// Add the backstage module to your executable\nexe.root_module.addImport(\"backstage\", backstage_dep.module(\"backstage\"));\n```\n\n### 2. Generate Proxies\n\nThe generator can be invoked to create proxy files, but adding the above configuration will generate the proxies on every build.\n\n```bash\nzig build gen-proxies\n```\n\nThis scans your source code for actors marked with `// @generate-proxy` and creates corresponding proxy files in the `src/generated` directory.\n\n### 3. Import and Use\n\n```zig\nconst MyActorProxy = @import(\"generated/my_actor_proxy.gen.zig\").MyActorProxy;\n\n// Use the proxy to interact with your actor\nconst actor = try engine.getActor(MyActorProxy, \"unique_actor_id\");\ntry actor.myMethod(parameters);\n```\n\n## Examples\n\nThe framework includes comprehensive examples demonstrating various patterns:\n\n- **hello_world_string.zig** - Basic actor with simple method calls\n- **hello_world_struct.zig** - Passing custom structs as parameters\n- **actor_to_actor.zig** - Direct actor-to-actor communication\n- **pub_sub.zig** - Publish/subscribe messaging with streams\n- **multiple_methods.zig** - Actors with multiple business methods\n- **large_struct.zig** - Handling complex data structures\n- **array_list_actor.zig** - Working with collections and dynamic data\n- **poison_pill.zig** - Actor shutdown patterns\n\nRun any example:\n\n```bash\ncd examples\nzig build hello_world_string\n```\n\n## Inspector\n\nThe inspector provides real-time visibility into your actor system through a graphical interface:\n\n### Enable Inspector\n\n```bash\nzig build -Denable_inspector=true\n```\n\nIn your `build.zig`:\n\n```zig\nconst backstage_dep = b.dependency(\"backstage\", .{\n    .target = target,\n    .optimize = optimize,\n    .enable_inspector = true,\n});\n\nif (enable_inspector) {\n    const inspector = backstage_dep.artifact(\"inspector\");\n    b.installArtifact(inspector);\n}\n```\n\n### Features\n\n- **Actor Metrics**: Monitor all active actors with their IDs and types\n- **Message Throughput**: Real-time messages per second for individual actors\n- **Performance Monitoring**: Rolling average throughput calculations\n- **Visual Interface**: Live updating graphical display of actor system state\n\n![Inspector Demo](actor_inspector.gif)\n\n## API Reference\n\n### Engine\n\nCore engine for managing the actor system:\n\n```zig\nvar engine = try backstage.Engine.init(allocator);\ndefer engine.deinit();\n\n// Get or create an actor\nconst actor = try engine.getActor(ActorProxy, \"actor_id\");\n\n// Get or create a stream\nconst stream = try engine.getStream(MessageType, \"stream_id\");\n\n// Run the event loop\ntry engine.loop.run(.once);  // Run once\ntry engine.loop.run(.until_done);  // Run until completion\n```\n\n### Context\n\nActor context provides access to other actors and streams:\n\n```zig\n// Get another actor\nconst other_actor = try self.ctx.getActor(OtherActorProxy, \"other_id\");\n\n// Get a stream\nconst stream = try self.ctx.getStream(DataType, \"stream_id\");\n\n// Access current actor ID\nconst my_id = self.ctx.actor_id;\n```\n\n### Streams\n\nType-safe publish/subscribe communication:\n\n```zig\n// Publisher\nconst stream = try ctx.getStream(MessageType, \"topic\");\ntry stream.next(message);\n\n// Subscriber\ntry stream.subscribe(backstage.newSubscriber(\"actor_id\", ActorProxy.Method.handler));\n```\n\n### Actor Requirements\n\nActors must implement:\n\n- `init(ctx: *Context, allocator: std.mem.Allocator) !*Self` - Constructor\n- `deinit(self: *Self) !void` - Destructor\n- Any number of public methods for business logic\n\n## Architecture\n\n### Code Generation\n\nThe proxy generator uses Zig's AST parser to:\n\n- Discover actors marked with `// @generate-proxy`\n- Extract public method signatures\n- Generate type-safe proxy wrappers\n- Handle automatic parameter serialization/deserialization\n\n### Method Dispatch\n\n- Method calls are serialized using CBOR\n- Calls are queued and executed asynchronously\n- Type safety is maintained through generated proxy interfaces\n\n### Memory Management\n\n- Actors are lazily initialized on first access\n- Efficient message passing with minimal allocations\n- Stream subscriptions are automatically managed\n\n## Performance\n\n- **Single-threaded design**: Eliminates locking overhead while maintaining concurrency\n- **Efficient serialization**: CBOR-based encoding for compact message format\n- **Event-driven I/O**: Built on libxev for high-performance networking\n\n## License\n\nMIT License - see LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomvanoorschot%2Fbackstage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthomvanoorschot%2Fbackstage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomvanoorschot%2Fbackstage/lists"}