{"id":22702399,"url":"https://github.com/phughesmcr/miski","last_synced_at":"2025-10-28T11:08:13.306Z","repository":{"id":39262415,"uuid":"328446772","full_name":"phughesmcr/Miski","owner":"phughesmcr","description":"An Archetypal Entity Component System (ECS) architecture in Typescript","archived":false,"fork":false,"pushed_at":"2023-05-07T22:29:47.000Z","size":2048,"stargazers_count":23,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-28T23:59:45.105Z","etag":null,"topics":["ecs","ecs-framework","engine","entity-component-system","game-development","gamedev","javascript","simulation","typescript"],"latest_commit_sha":null,"homepage":"https://phughesmcr.github.io/Miski/","language":"TypeScript","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/phughesmcr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null},"funding":{"github":"phughesmcr","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-01-10T18:04:32.000Z","updated_at":"2024-07-17T21:17:45.000Z","dependencies_parsed_at":"2023-02-17T04:10:25.150Z","dependency_job_id":null,"html_url":"https://github.com/phughesmcr/Miski","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phughesmcr%2FMiski","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phughesmcr%2FMiski/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phughesmcr%2FMiski/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phughesmcr%2FMiski/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phughesmcr","download_url":"https://codeload.github.com/phughesmcr/Miski/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229019441,"owners_count":18007169,"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":["ecs","ecs-framework","engine","entity-component-system","game-development","gamedev","javascript","simulation","typescript"],"created_at":"2024-12-10T07:13:20.286Z","updated_at":"2025-10-28T11:08:13.279Z","avatar_url":"https://github.com/phughesmcr.png","language":"TypeScript","funding_links":["https://github.com/sponsors/phughesmcr"],"categories":[],"sub_categories":[],"readme":"# 🍬 Miski ECS\n\n__Miski__: Quechuan adjective meaning \"sweet\".\n\n__ECS__: Entity-Component-System; a software architecture pattern.\n\n__Miski ECS__: A sweet, high-performance ECS library written in Typescript.\n\nSee [jsr.io/@phughesmcr/miski](https://jsr.io/@phughesmcr/miski) for complete documentation.\n\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"https://badgen.net/badge/license/MIT/blue\" alt=\"MIT License\" /\u003e\n  \u003cimg src=\"https://badgen.net/badge/icon/typescript?icon=typescript\u0026label\" alt=\"Written in Typescript\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/deno-^2.1.0-lightgrey?logo=deno\" alt=\"Deno version\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/bun-%5E1.1.0-lightgrey?logo=bun\" alt=\"Bun version\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node-%5E22.0.0-lightgrey?logo=node.js\" alt=\"Node version\" /\u003e\n\u003c/p\u003e\n\n*Note*: The runtime versions above are indicative, Miski should work on any modern JavaScript runtime.\n\n## Purpose\n\nMiski's purpose is to provide a performant, stable, developer-friendly ECS architecture for modern web projects.\n\nSince ECS libraries are primarily used in games and other performance-critical applications, performant here means:\n\n* Miski aims to minimize garbage collection and memory allocation pressure, reducing the risk of dropped frames.\n* Miski takes advantage of web standards like `ArrayBuffer` and `WeakMap` to provide fast, cache-friendly component storage, querying, and iteration.\n\nStable here means:\n\n* The API will not change meaningfully.\n* The results produced by the library are predictable and consistent.\n* No 3rd-party dependencies.\n\nDeveloper-friendly here means:\n\n* The library is well-documented, self-documenting, and has a clean, readable codebase.\n* The library is easy to understand, learn, and use.\n* The library is easy to extend, customize, and integrate into existing projects.\n\n### Goals\n\n* To provide good and predictable performance\n* To provide a developer-friendly API\n* To provide a clean, readable, self-documenting, open-source codebase\n\n### Not Goals\n\nBecause Miski is designed to be used inside your own projects, we let you configure bundling and performance tuning to suit your needs, therefore the following are not priorities of this project:\n\n* To be the fastest or smallest ECS on the web\n* To provide an API that is interchangeable with other ECS libraries\n* To provide polyfills, workarounds, or older browser support for modern ECMAScript features\n\n## Features\n\n* Modern modular ES2022 data-oriented Typescript codebase\n* Fast, cache-friendly ArrayBuffer-based component data storage\n* Simple, developer-friendly, human-readable API\n* Ability to register more than 32 components in one world\n* Ability to limit the number of entities a component can be added to\n* Define components, systems and queries once, reuse them across multiple worlds\n* `AND`,`OR`,`NOT` operators in Queries\n* `world.getQueryEntered` \u0026 `world.getQueryExited` methods\n* Use `component.changed` to get an iterator of entities whose properties were changed via `component.proxy`\n* No 3rd-party dependencies\n* MIT license\n\n## Installation\n\n### Node\n\n```bash\nnpx jsr add @phughesmcr/miski\n```\n\n```ts\nimport { World, ... } from \"@phughesmcr/miski\";\n```\n\n### Deno\n\n```bash\ndeno add jsr:@phughesmcr/miski\n```\n\n```ts\nimport { World, ... } from \"@phughesmcr/miski\";\n```\n\n### Bun\n\n```bash\nbunx jsr add @phughesmcr/miski\n```\n\n```ts\nimport { World, ... } from \"@phughesmcr/miski\";\n```\n\n## Quick Start API Reference\n\nBelow are the essentials of the Miski API. For full API documentation see [jsr.io/@phughesmcr/miski](https://jsr.io/@phughesmcr/miski).\n\nEach concept in this reference builds on the previous concept, it should be read in order.\n\n### World\n\nThe world object is the primary container for all things Miski.\n\nWe can create a new world like so:\n\n```typescript\nconst world = new World({\n  capacity: 1000, // The maximum number of entities to allow in the world\n  components: [\n    positionComponent, // We'll create this in the components section below\n  ],\n});\n```\n\n\u003cspan style=\"background-color: #aa0010; color: #ffffff; padding: 4px; border-radius: 4px;\"\u003e\n⚠️ Components cannot be added to a world after its creation.\n\u003c/span\u003e\n\n\u0026nbsp;\n\n\u003cspan style=\"background-color: #1000aa; color: #ffffff; padding: 4px; border-radius: 4px;\"\u003e\nℹ️ The world requires frequent maintenance (usually once per frame):\n\u003c/span\u003e\n\n\u0026nbsp;\n\n```typescript\nworld.refresh();\n```\n\n### Components\n\nA component is a data structure that gives entities their state.\n\nComponents can be created once and used across multiple worlds.\n\nFor example, to create a 2d position component:\n\n```typescript\n// Optional schema:\ntype Vec2 = { x: Float32ArrayConstructor, y: Float32ArrayConstructor }; // defines what input we want (number only)\n\nconst positionComponent = new Component\u003cVec2\u003e({\n  // ⚠️ There are some names you cannot use for components or their schema properties. \n  // You can use `isValidName()` to check if a name is valid.\n  name: \"position\",\n\n  // The schema relates to the input type above, in this case Vec2.\n  // It defines how we want to store the expected datatype (number).\n  // Since we know a Vec2 requires X and Y to be Float32Array, we can define the schema like so:\n  schema: {\n    x: Float32Array,\n    y: Float32Array,\n  },\n});\n```\n\n#### Tags\n\nWe can create a tag component by omitting the schema object and (optionally) providing a null type:\n\n```typescript\nconst activeComponent = new Component\u003cnull\u003e({\n  name: \"active\"\n});\n```\n\n#### MaxEntities\n\nBy default a component can be added to as many entities as the world's capacity, we can change this behaviour like so:\n\n```typescript\nconst player = new Component\u003cnull\u003e({\n  name: \"player\",\n  maxEntities: 1,\n});\n```\n\n#### Adding and Removing Components\n\nWe can add and remove components from entities like so:\n\n```typescript\n// Add the component to an entity:\nworld.components.addToEntity(positionComponent, entity);\n\n// Add with initial data:\nworld.components.addToEntity(positionComponent, entity, { x: 10, y: 20 });\n```\n\n```typescript\n// Remove the component from an entity:\nworld.components.removeFromEntity(positionComponent, entity);\n```\n\n#### Test for Component presence\n\nWe can also test if entities have components:\n\n```typescript\n// Check if an entity has a component\nconst hasPosition: boolean = world.components.entityHas(positionComponent, entity);\n```\n\n#### Modifying an Entity's Component properties\n\nTo access the component's data from a specific world, we have to get the ComponentInstance, like so:\n\n```typescript\n// returns ComponentInstance\u003cT\u003e or undefined\nconst positionInstance = world.components.getInstance(positionComponent);\n\n// For multiple components:\nconst instances = world.components.getInstances([positionComponent, ...]);\n```\n\n\u003cspan style=\"background-color: #1000aa; color: #ffffff; padding: 4px; border-radius: 4px;\"\u003e\nℹ️ The component instance is accessible quickly using Systems (see below).\n\u003c/span\u003e\n\n\u0026nbsp;\n\nOnce we have the component instance we can modify entity properties.\n\nThere are two ways to do this:\n\nThe first is quick but unsafe (no change tracking):\n\n```typescript\npositionInstance.storage.partitions.x[entity] = 1;\n```\n\nThe second is slower but safer (with change tracking and type guards):\n\n```typescript\npositionInstance.proxy.entity = entity;\npositionInstance.proxy.x = 1;\n```\n\nThe second way, using `.proxy` has the advantage of also adding the entity to the changed tracking as well as performing some basic typeguarding.\n\nFor example:\n\n```typescript\n// Direct storage access - no change tracking\npositionInstance.storage.partitions.x[101] = 1;\n\n// Proxy access - with change tracking\npositionInstance.proxy.entity = 444;\npositionInstance.proxy.x = 1;\n\n// Only entity 444 appears in changed tracking\nconst changed = world.components.getChanged(positionComponent);\nfor (const entity of changed) {\n  console.log(entity); // 444 only, not 101\n}\n```\n\n\u003cspan style=\"background-color: #1000aa; color: #ffffff; padding: 4px; border-radius: 4px;\"\u003e\nℹ️  The `changed` tracking is reset with every `world.refresh()`.\n\u003c/span\u003e\n\n\u0026nbsp;\n\nYou can also access the changed entities of a component like so:\n\n```typescript\nconst changed = world.components.getChanged(positionComponent);\n```\n\n### Entities\n\nEntities are just integers. They are essentially indexes or pointers into various arrays in the world.\n\n```typescript\n// Create (will return undefined if no entities are available)\nconst entity = world.entities.create();\n// Destroy\nworld.entities.destroy(entity);\n// Test if entity is active in the world\nworld.entities.isActive(entity);\n// Test if an entity is valid in the world\nworld.entities.isEntity(4235); // will return false if the world capacity is 1000 as above\n// Get the number of active entities in a world\nconst active = world.entities.getActiveCount();\n// Get the number of remaining available entities in a world\nconst available = world.entities.getAvailableCount();\n```\n\n### Queries\n\nQueries help us to find relationships between entities and components.\n\n```typescript\nconst positionQuery = new Query({\n  all: [positionComponent],\n  any: [...],\n  none: [...],\n});\n```\n\nWe can then access the entities and components which match our query:\n\n```typescript\nconst components = world.components.query(positionQuery);\nconst entities = world.entities.query(positionQuery);\n```\n\nWe can also access entities which have entered or exited the query since the last `world.refresh()`:\n\n```typescript\nconst entered = world.archetypes.queryEntered(positionQuery);\nconst exited = world.archetypes.queryExited(positionQuery);\n```\n\n\n### Systems\n\nSystems are functions which use queries to modify entity properties.\n\nIt is recommended (but not necessary) that all data mutation take place inside a system.\n\n```typescript\nconst positionSystem = new System({\n  name: \"positionSystem\",\n  query: positionQuery,\n  callback: (components, entities) =\u003e {\n    const { position } = components;\n    const { x, y } = position.storage.partitions;\n    for (const entity of entities) {\n      x[entity] += 1;\n      y[entity] += 1;\n    }\n  },\n});\n```\n\nOnce created a system can be registered with the world:\n\n```typescript\nconst systemInstance = world.systems.create(positionSystem);\n```\n\nOnce registered, systems are then called like normal functions:\n\n```typescript\nsystemInstance();\n```\n\n\n## Contributing\n\nContributions are welcome and encouraged. The aim of the project is performance - both in terms of speed and GC allocation pressure.\n\nPlease run `deno test`, `deno bench` and `deno task prep` to run tests, benchmarks, and formatting before committing.\n\n## Feature Requests\n\nFeature requests are welcome and invited. Please open an issue on Github to make a request.\n\n## Acknowledgements\n\nMiski is inspired by [ape-ecs](https://github.com/fritzy/ape-ecs), [BECSY](https://github.com/LastOliveGames/becsy), [bitECS](https://github.com/NateTheGreatt/bitECS), [ECSY](https://github.com/ecsyjs/ecsy), [Geotic](https://github.com/ddmills/geotic), [HECS](https://github.com/gohyperr/hecs), [Wolf ECS](https://github.com/EnderShadow8/wolf-ecs), and [Structurae](https://github.com/zandaqo/structurae).\n\n## License\n\nMiski is released under the MIT license. See `LICENSE` for further details.\n\n\u0026copy; 2024 The Miski Authors. All rights reserved.\n\nSee `AUTHORS.md` for author details.\n****\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphughesmcr%2Fmiski","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphughesmcr%2Fmiski","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphughesmcr%2Fmiski/lists"}