{"id":16817764,"url":"https://github.com/igr/tatsugo","last_synced_at":"2025-03-17T13:24:02.765Z","repository":{"id":257307785,"uuid":"836198384","full_name":"igr/tatsugo","owner":"igr","description":"Event-Driven framework with handlers, queues and actors.","archived":false,"fork":false,"pushed_at":"2024-10-03T16:45:03.000Z","size":204,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-10T10:48:05.670Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-07-31T10:57:42.000Z","updated_at":"2024-10-03T16:45:46.000Z","dependencies_parsed_at":"2024-09-15T21:58:07.910Z","dependency_job_id":"51fc6fb8-c01b-4584-9f70-23718863c56f","html_url":"https://github.com/igr/tatsugo","commit_stats":null,"previous_names":["igr/tatsugo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igr%2Ftatsugo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igr%2Ftatsugo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igr%2Ftatsugo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igr%2Ftatsugo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igr","download_url":"https://codeload.github.com/igr/tatsugo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244039202,"owners_count":20387835,"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","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":[],"created_at":"2024-10-13T10:48:11.538Z","updated_at":"2025-03-17T13:24:02.740Z","avatar_url":"https://github.com/igr.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🥔 Tatsugō\n\n**Tatsugō** is a lightweight Event-driven engine written in Kotlin, that combines **events**, **queues** and **actors**.\n\nEnables _four million of events per second_ between 10k components (on my M2).\n\nFurthermore, it allows state isolation and writing lock-free code.\n\n## Overview\n\n**Tatsugō** offers a couple of abstractions.\n\n🚌 **Bus** is a simple event bus that broadcasts events to all subscribers.\n\n🎭 **Event handler** is a simple, traditional event processor.\nEvent Handler process an event and return a list of new events that should be emitted.\nThis is one distinct feature of **Tatsugō**:\nevents are never sent directly within the function's code, but provided as the returned value.\nFurthermore, there is no guarantee when the events will be processed and in which order regarding the other components.\n\n🧵 **Queue** is a queued event handler.\nIt filters the events and _queues_ them internally.\nThe queued events are processed in serial order.\nUsually, one queue instance processes all its events.\n\n⚛️ **Particle** is an isolated unit of work; with address, behaviour and, usually, internal (isolated) state.\nParticle reminds of actor (and probably _is_ actor:).\nParticles communicate via _messages_.\nA **message** is an event enhanced with the target particle address.\nParticles are never created and run directly.\n\n🚢 **Fleet** is a queue for Particles.\nFleet manages particles: it creates, destroys, and runs them.\nAll messages are processed in serial order.\n\n## Usage\n\nWith **Tatsugō**, you essentially write event-driven code.\nHowever, the idea is to write a code that is easy to reason about,\nwithout using _any_ synchronization code!\nThe state should be isolated and there is no need for locks.\n\nEssentially, you have three options where to put your code:\n\n1. **Event handler** is a simple event processor.\nIt should be used only for \"global\" tasks, like logging, metrics, etc.\nThere is no guarantee when the event will be processed.\n2. **Queue** is usually a singleton that takes care of some global states. It processes events in serial order. The queue code is lock-free!\n3. **Particle** represents the behavior around a single state instance. Each particle has a unique address (e.g., a database row id.)\nThere could (and should) be a _lot_ of particles running at once!\nSince they operate only with their own state, they are isolated and lock-free.\n\nLet's go through the example:\n\n**① Start with a Bus**\n\n`Bus` in **Tatsugō** is a simple interface, so you need to choose the implementation. There are two available: one that works with `SharedFlow` and another with `Channel`. The second one is more performant.\n\nEach implementation has its own way of how to start the bus.\n\n```kt\nval bus = startChannelBus()\n```\n\n**② Add Event Handler (optionally)**\n\nYou can use traditional event handlers to process events, but try to avoid them for the business logic.\n\n```kt\nbus.subscribe(MyHandler())\n```\n\n**③ Add singleton Queues**\n\nQueues should be used for components that maintain some global states.\nThink of them as a singleton services that are responsible for e.g., a database table(s).\n\n```kt\nval myq = MyQ()\nbus.bind(object : Queue {\n    override fun isApplicable(event: Event): Boolean =\n        event is MyQ.FooEvent\n    override fun process(event: Event): Array\u003cEvent\u003e =\n        myq.on(event as MyQ.FooEvent)\n})\n```\n\n**④ Create a Fleet and define Particles**\n\nParticles should encapsulate the state and wrap the behaviour around it, same as actors.\nFleet is responsible for managing the particles.\n\n```kt\nval fleet = spawnSimpleFleet(\"bars\")\nbus.bind(fleet.asQueue())\nfleet.bind(BarLifecycle())\n\n// Particle\nclass Bar(\n\tval address: ParticleAddress,\n\tval fleetRef: FleetRef,\n\tval state: BarState,\n\tval behavior: (Bar, Msg) -\u003e NextParticle\n) : Particle {}\n```\n\n## Example: Game of Life\n\nWe have the following components:\n\n+ ⚛️ `Cell` is a _particle_ that represents a single cell in the game.\nIt has a state: alive or dead (and the address, of course.)\nEach particle only knows about its own state.\nAs particles cannot exist on their own, they are created and managed by the Fleet.\n+ 🧵 `Grid` is a _queue_ that manages the cells, i.e., the board.\nIt is a singleton unit that receives information of the cells' state changes.\nOn each cell change event, `Grid` sends a notification message to all neighbors of the changed cell to its fleet.\n+ 🎭 `StatsCounter` - simple event handler that counts the events and prints the statistics at the end of the game, after the last generation.\n\nThe example:\n\n+ grid size is `100x100`\n+ number of generations is `100`\n+ total number of events: `8 900 400` 🔥\n+ total execution time is `2.2s` (average) on my machine (M2) 🚀\n+ the code is lock-free! No synchronization primitives are used. 🤩\n\n![](gol.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figr%2Ftatsugo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figr%2Ftatsugo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figr%2Ftatsugo/lists"}