{"id":15008153,"url":"https://github.com/sdleffler/hv-dev","last_synced_at":"2025-10-30T12:31:24.929Z","repository":{"id":41846049,"uuid":"421605155","full_name":"sdleffler/hv-dev","owner":"sdleffler","description":"Slow down, upon the teeth of Orange: Heavy is an opinionated, efficient, relatively lightweight, and tightly Lua-integrated game framework for Rust.","archived":false,"fork":false,"pushed_at":"2022-05-11T05:20:11.000Z","size":996,"stargazers_count":14,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-02T08:32:17.599Z","etag":null,"topics":["ecs","gamedev","lua","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sdleffler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-26T22:43:31.000Z","updated_at":"2024-01-07T13:11:28.000Z","dependencies_parsed_at":"2022-08-11T19:11:06.625Z","dependency_job_id":null,"html_url":"https://github.com/sdleffler/hv-dev","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdleffler%2Fhv-dev","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdleffler%2Fhv-dev/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdleffler%2Fhv-dev/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdleffler%2Fhv-dev/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sdleffler","download_url":"https://codeload.github.com/sdleffler/hv-dev/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238968302,"owners_count":19560586,"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","gamedev","lua","rust"],"created_at":"2024-09-24T19:15:24.167Z","updated_at":"2025-10-30T12:31:24.555Z","avatar_url":"https://github.com/sdleffler.png","language":"Rust","readme":"# Heavy - an opinionated, efficient, relatively lightweight, and tightly Lua-integrated game framework for Rust\n\n*Slow down, upon the teeth of Orange*\n\nHeavy is a mostly backend/platform-agnostic game framework for Rust, with the target of providing\nefficient primitives for game development while also remaining tightly integrated with Lua scripting.\n\nIt consists of two main parts:\n- The `hv` crate, an aggregation of modular sub-crates which implement a Lua interface and wrap it\n  around a number of common utilities (ECS, math, input mapping, etc.) without being tied in\n  specific to a rendering strategy.\n- Altar, an engine built on `hv` for building top-down 2.5D games, using `luminance` for\n  platform-agnostic graphics and supporting multiple underlying windowing libraries.\n  \nAt current there is also a local fork of `hecs` adding support for dynamic queries and exposing some\nparts of the `hecs` system which were previously hidden. Hopefully to be upstreamed. The scheduler\n(`yaks`) is dependent on `hecs`, so we have a local fork of it too.\n\n## Why?\n\nCurrent Rust game frameworks don't have first-class support for things like Lua integration, and\nintegrating a crate like `mlua` with external crates is made complicated by Rust's orphan rules;\nit's a compiler error to try to implement `mlua::UserData` for say, `hecs::World`. By forking\n`mlua`, we get the ability to add first-class support for the notion of a Rust *type* reified as\nuserdata (through `hv::lua::Lua::create_type_userdata`.) This is done through the incredibly cursed\n`hv-alchemy` crate.\n\nIn other words, the goal of Heavy is to provide a Rusty, efficient interface, which is also tightly\nintegrated with scripting for fast iteration and moddability down the road, as well as for working\nwith non-coding artists and less-technical team members who find working with a scripting language\nlike Lua easier to deal with than a language with a high learning curve and domain knowledge\nrequirement (Rust.)\n\n```\nAlso, I'm an idiot, so I like making game frameworks/engines.\n- sleff\n```\n\n### `hv` - Features\n\nThese are all in progress/goals:\n\n- `hecs`-based ECS with `yaks`-based executor/system scheduler.\n- Lua integration based on a custom fork of `mlua`, providing easy and powerful integration with\n  Rust traits and types thanks to `hv-alchemy`.\n  - `hv-` crates integrated w/ Lua by default, w/ runtime reflection support for creating and\n    manipulating Rust types from Lua with minimal (but present) boilerplate:\n    - `hv-math` (`nalgebra` and goodies)\n    - `hecs` (ECS, entity spawning and querying)\n    - `hv-filesystem` (virtual filesystem)\n    - `hv-alchemy` (runtime trait object registration and manipulation)\n    - `hv-input` (input mappings and state)\n  - Support for \"Rust type userdata objects\" through `hv-alchemy` and Alchemical reflection on\n    `AnyUserData` objects.\n- Synchronization primitives and other goodies useful for interfacing with Lua.\n- (TODO) audio through FMOD.\n- Portability limited only by the Rust standard library and Lua (and eventually FMOD).\n\n### `altar` - Features\n\n- Implemented with `hv` at its core.\n- Abstracted external events.\n- Abstracted rendering provided by `luminance`.\n- Portability limited only by the Rust standard library, Lua, and luminance.\n\n## Motivating example: defining a Lua interface for a component type, spawning entities in and querying the ECS from Lua\n\nConnecting a component type w/ `hv` is done through the `UserData` trait. Here's a contrived but\nultra-simple example implementation for a component which just wraps an `i32`:\n\n```rust\n/// A component type wrapping an `i32`; for technical reasons, primitives cannot be viewed as\n/// components from Lua (because they can't implement `UserData`.)\n#[derive(Debug, Clone, Copy)]\nstruct I32Component(i32);\n\n// The `UserData` impl defines how the type interacts with Lua and also what methods its type object\n// has available.\nimpl UserData for I32Component {\n    // We allow access to the internal value via a Lua field getter/setter pair.\n    #[allow(clippy::unit_arg)]\n    fn add_fields\u003c'lua, F: UserDataFields\u003c'lua, Self\u003e\u003e(fields: \u0026mut F) {\n        fields.add_field_method_get(\"value\", |_, this| Ok(this.0));\n        fields.add_field_method_set(\"value\", |_, this, value| Ok(this.0 = value));\n    }\n\n    // Rust simply does not have compile-time reflection. The `on_metatable_init` method provides\n    // the ability to register traits we need at run-time for this type; it also doubles as a way of\n    // requiring Rust to generate the code for the vtables of those traits (which would not\n    // otherwise happen if they were not actually used.) `.mark_component()` comes from the\n    // `LuaUserDataTypeExt` trait which provides convenient shorthand for registering required\n    // traits; in this case, `mark_component` registers `dyn Send` and `dyn Sync` impls which are\n    // sufficient to act as a component.\n    fn on_metatable_init(t: Type\u003cSelf\u003e) {\n        t.add_clone().add_copy().mark_component();\n    }\n\n    // The following methods are a bit like implementing `UserData` on `Type\u003cSelf\u003e`, the userdata\n    // type object of `Self`. This one just lets you construct an `I32Component` from Lua given a\n    // value convertible to an `i32`.\n    fn add_type_methods\u003c'lua, M: UserDataMethods\u003c'lua, Type\u003cSelf\u003e\u003e\u003e(methods: \u0026mut M) {\n        methods.add_function(\"new\", |_, i: i32| Ok(Self(i)));\n    }\n\n    // We want to generate the necessary vtables for accessing this type as a component in the ECS.\n    // The `LuaUserDataTypeTypeExt` extension trait provides convenient methods for registering the\n    // required traits for this (`.mark_component_type()` is shorthand for\n    // `.add::\u003cdyn ComponentType\u003e()`.)\n    fn on_type_metatable_init(t: Type\u003cType\u003cSelf\u003e\u003e) {\n        t.mark_component_type();\n    }\n}\n```\n\nNow given this, we can write something like this:\n\n```rust\n// Create a Lua context.\nlet lua = Lua::new();\n// Load some builtin `hv` types into the Lua context in a global `hv` table (this is going to \n// change; I'd like a better way to do this)\nlet hv = hv::lua::types(\u0026lua)?;\n\n// Create userdata type objects for the `I32Component` defined above as well as a similarly defined\n// `BoolComponent` (exercise left to the reader)\nlet i32_ty = lua.create_userdata_type::\u003cI32Component\u003e()?;\nlet bool_ty = lua.create_userdata_type::\u003cBoolComponent\u003e()?;\n\n// To share an ECS world between Lua and Rust, we'll need to wrap it in an `Arc\u003cAtomicRefCell\u003c_\u003e\u003e`.\n// Heavy provides other potentially more efficient ways to do this sharing but this is sufficient\n// for this example.\nlet world = Arc::new(AtomicRefCell::new(World::new()));\n// Clone the world so that it doesn't become owned by Lua. We still want a copy!\nlet world_clone = world.clone();\n\n// `chunk` macro allows for in-line Lua definitions w/ quasiquoting for injecting values from Rust.\nlet chunk = chunk! {\n    // Drag in the `hv` table we created above, and also the `I32Component` and `BoolComponent` types,\n    // presumptuously calling them `I32` and `Bool` just because they're wrappers around the fact we\n    // can't just slap a primitive in there and call it a day.\n    local hv = $hv\n    local Query = hv.ecs.Query\n    local I32, Bool = $i32_ty, $bool_ty\n\n    local world = $world_clone\n    // Spawn an entity, dynamically adding components to it taken from userdata! Works with copy,\n    // clone, *and* non-clone types (non-clone types will be moved out of the userdata and the userdata\n    // object marked as destructed)\n    local entity = world:spawn { I32.new(5), Bool.new(true) }\n    // Dynamic query functionality, using our fork's `hecs::DynamicQuery`.\n    local query = Query.new { Query.write(I32), Query.read(Bool) }\n    // Querying takes a closure in order to enforce scope - the queryitem will panic if used outside that\n    // scope.\n    world:query_one(query, entity, function(item)\n        // Querying allows us to access components of our item as userdata objects through the same interface\n        // we defined above!\n        assert(item:take(Bool).value == true)\n        local i = item:take(I32)\n        assert(i.value == 5)\n        i.value = 6\n        assert(i.value == 6)\n    end)\n\n    // Return the entity we spawned back to Rust so we can examine it there.\n    return entity\n};\n\n// Run the chunk and get the returned entity.\nlet entity: Entity = lua.load(chunk).eval()?;\n\n// Look! It worked!\nlet borrowed = world.borrow();\nlet mut q = borrowed\n    .query_one::\u003c(\u0026I32Component, \u0026BoolComponent)\u003e(entity)\n    .ok();\nassert_eq!(\n    q.as_mut().and_then(|q| q.get()).map(|(i, b)| (i.0, b.0)),\n    Some((6, true))\n);\n```\n\nWithout `hv`, doing this would require a massive amount of boilerplate for the component types,\nwrapping a `hecs::World` in your own custom userdata type that supports this type-based\nmanipulation since as a foreign type you can't impl `mlua::UserData` directly, wrapping\n`hecs::Entity` etc., writing dynamic wrappers around everything required to insert a component of a\ngiven type on a per-component basis (Heavy boils this all down into `.add::\u003cdyn ComponentType\u003e()`),\nwrappers for a *borrowed* world, ensuring that the Lua code transparently shows where it borrows and\nunborrows the world, etc.\n\nSo while there's still some unavoidable boilerplate, it's a lot less of a mess and allows for\nwriting Lua interaction code as if you're directly interacting with a given type rather than with\nyour own custom wrapper boilerplate.\n\n## Compiling\n\nThis repository has two submodules, `hv-ecs` (our `hecs` fork) and `hv-lua` (our `mlua` fork.) These\ntwo crates are kept in separate repositories per fork so that it's easier to pull changes from\nupstream/rebase onto upstream. Packages in this repository will depend on them via path\ndependencies, and they depend back on this repository via path dependencies, assuming that they live\nin the same directory structure as here; so if you're hacking on them it's probably a good idea to\nstart by cloning `hv-dev`. As a result, the first thing you need to do is:\n\n```\ngit clone --recursive https://github.com/sdleffler/hv-dev\n```\n\nNext, by default we depend on LuaJIT. You will want to install LuaJIT 2.0.5 (latest stable as of the\ntime of writing) and set environment variables `LUA_INC`, `LUA_LIB`, and `LUA_LIBDIR` accordingly.\n`LUA_INC` will need to be the path to your Lua header files, which is likely the `src` subfolder of\nyour LuaJIT download; `LUA_LIBDIR` will be the same path as long as you don't move anything after\ncompiling LuaJIT; and `LUA_LIB` will be `lua51` if you use LuaJIT, or potentially `lua52` if using\nLuaJIT partial 5.2 compatibility.\n\nOnce you have this, you should be good to go. In the future, FMOD will be added as a dependency and\nwill also need to be installed locally.\n\n## License\n\nLicensed under either of\n\n * Apache License, Version 2.0\n   ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license\n   ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n## Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdleffler%2Fhv-dev","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsdleffler%2Fhv-dev","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdleffler%2Fhv-dev/lists"}