{"id":"thelightsaberthatisblue-cell/object","name":"object","scope":"thelightsaberthatisblue-cell","platform":"roblox","description":"A reactive OOP entity framework for Roblox Luau. Supports inheritance, mixins, signals, HyperSync reactive state, generation trees, adornee pooling, and full instance lifecycle management.","version":"1.1.0","latest":"1.1.0","versions":["0.1.0","1.0.0","1.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"d2b0b61d9fbde9cde77efd904662787cc1cd8bd52a9d7ecaf36b0a4de8b86834","likes":0,"downloads":0,"install":"forest install thelightsaberthatisblue-cell/object","url":"https://forest.dev/p/roblox/thelightsaberthatisblue-cell/object","files":"https://api.forest.dev/ai/package/roblox/thelightsaberthatisblue-cell/object/files","readme":"# Object\r\nA reactive OOP entity framework for Roblox Luau.\r\n\r\nObject gives you a clean, production-ready class system with inheritance, signals, reactive state, generation trees, and full instance lifecycle management — all with a simple, consistent API.\r\n\r\n---\r\n\r\n## Installation\r\n\r\nAdd to your `wally.toml`:\r\n\r\n```toml\r\n[dependencies]\r\nObject = \"thelightsaberthatisblue-cell/object@1.0.0\"\r\n```\r\n\r\nThen require it in your script:\r\n\r\n```lua\r\nlocal Object = require(ReplicatedStorage.Packages.Object)\r\n```\r\n\r\n---\r\n\r\n## Core Concepts\r\n\r\n| Concept | What it is |\r\n|---|---|\r\n| **Class** | A blueprint. Defines variables, methods, signals, and lifecycle hooks. |\r\n| **Instance** | A live object created from a class. Has its own independent state. |\r\n| **Adornee** | The object the instance is bound to. Not limited to Roblox Instances. |\r\n| **Main** | Auto-runs when an instance is created. |\r\n| **SetCleanup** | Auto-runs before an instance is destroyed. The reverse of Main. |\r\n\r\n---\r\n\r\n## Quick Start\r\n\r\n```lua\r\nlocal Object = require(ReplicatedStorage.Packages.Object)\r\n\r\n-- define a class\r\nlocal Enemy = Object.New(\"Enemy\")\r\n\r\nEnemy.NewVar(\"Health\", 100)\r\nEnemy.NewVar(\"Name\", \"Enemy\", \"immutable\")\r\n\r\nEnemy.NewFunc(\"TakeDamage\", function(self, dmg)\r\n    self.Health -= dmg\r\nend)\r\n\r\nEnemy.SetMain(function(self)\r\n    print(self.Name .. \" spawned with \" .. self.Health .. \" HP\")\r\nend)\r\n\r\n-- create an instance\r\nlocal goblin = Enemy.New(workspace.GoblinPart)\r\ngoblin:TakeDamage(25)\r\nprint(goblin.Health) -- 75\r\n```\r\n\r\n---\r\n\r\n## Class Definition API\r\n\r\n### `Object.New(className)`\r\nCreates a new root class.\r\n```lua\r\nlocal Enemy = Object.New(\"Enemy\")\r\n```\r\n\r\n### `Class:Extend(childName)`\r\nCreates a child class that inherits everything from the parent.\r\n```lua\r\nlocal Zombie = Enemy:Extend(\"Zombie\")\r\nlocal FastZombie = Zombie:Extend(\"FastZombie\")\r\n```\r\n\r\n### `Object.GetClass(className)`\r\nRetrieves any class from anywhere in your codebase by name.\r\n```lua\r\nlocal Enemy = Object.GetClass(\"Enemy\")\r\n```\r\n\r\n---\r\n\r\n## Variables\r\n\r\n### `Class.NewVar(name, value, mode?)`\r\nDefines a variable on the class.\r\n\r\n| Mode | Behavior |\r\n|---|---|\r\n| `\"copy\"` (default) | Deep copied per instance. Each instance gets its own independent value. Use for mutable state like `Health`, cooldowns, position offsets. |\r\n| `\"ref\"` | Shared reference across all instances. Changes on one instance affect all. Use for shared config tables you want to update globally. |\r\n| `\"immutable\"` | Shared reference, write-protected. Errors if any instance tries to overwrite it. Use for constants like `MaxHealth`, `Name`, `Damage`. |\r\n\r\n```lua\r\nEnemy.NewVar(\"Health\", 100)                    -- copy (default)\r\nEnemy.NewVar(\"MaxHealth\", 100, \"immutable\")    -- shared, locked\r\nEnemy.NewVar(\"Config\", difficultyTable, \"ref\") -- shared, mutable\r\n```\r\n\r\n---\r\n\r\n## Methods\r\n\r\n### `Class.NewFunc(name, func)`\r\nStandard method. Receives `(self, ...)`.\r\nUse `self:Super()` to call the parent class version (injection style).\r\n```lua\r\nEnemy.NewFunc(\"TakeDamage\", function(self, dmg)\r\n    self.Health -= dmg\r\nend)\r\n```\r\n\r\n### `Class.NewSuperFunc(name, func)`\r\nSuper-arg style. Receives `(self, super, ...)`.\r\n`super` is the immediate parent's version of this method, passed as a plain argument.\r\nZero instance mutation. Preferred over `self:Super()` in performance-sensitive code.\r\n```lua\r\nZombie.NewSuperFunc(\"TakeDamage\", function(self, super, dmg)\r\n    super()            -- calls Enemy:TakeDamage(self)\r\n    self.Health -= 5   -- zombie takes extra damage\r\nend)\r\n```\r\n\r\n### `Class.NewHyperFunc(name, targetClass, func)`\r\nHyper-arg style. Receives `(self, hyper, ...)`.\r\n`hyper` is a specific ancestor's version, skipping everything in between.\r\n```lua\r\n-- FastZombie wants Enemy's TakeDamage, skipping Zombie's version\r\nFastZombie.NewHyperFunc(\"TakeDamage\", \"Enemy\", function(self, hyper, dmg)\r\n    hyper()\r\n    self.Health -= dmg\r\nend)\r\n```\r\n\r\n### `Class:Include(mixin)`\r\nInjects a flat table of functions into the class as methods.\r\nUseful for shared behavior across unrelated classes (flying, poison, stealth).\r\n```lua\r\nlocal FlyMixin = {\r\n    Fly = function(self)\r\n        print(self.Adornee.Name .. \" is flying!\")\r\n    end\r\n}\r\n\r\nZombie:Include(FlyMixin)\r\ngoblin:Fly()\r\n```\r\n\r\n---\r\n\r\n## Lifecycle\r\n\r\n### `Class.SetMain(func)`\r\nRuns automatically when an instance is created. `self` is the instance.\r\n```lua\r\nEnemy.SetMain(function(self)\r\n    print(\"spawned!\")\r\nend)\r\n```\r\n\r\n### `Class.SetCleanup(func)`\r\nRuns before the instance is destroyed. `self` is still fully intact.\r\nUse this to clean up coroutines, external connections, or anything you created in Main.\r\n```lua\r\nEnemy.SetCleanup(function(self)\r\n    self.AICoroutine:Cancel()\r\n    self.ExternalConnection:Disconnect()\r\nend)\r\n```\r\n\r\n---\r\n\r\n## Adornee\r\n\r\nThe adornee is the object an instance is bound to — not strictly limited to Roblox Instances. It can be a Part, Model, ScreenGui, or any runtime value.\r\n\r\nWhen a Roblox Instance adornee is destroyed, the instance is automatically destroyed too. Non-Instance adornees have no auto-destroy binding — the developer manages their lifecycle manually or via `SetCleanup`.\r\n\r\n### Manual adornee (developer passes it)\r\n```lua\r\nlocal goblin = Enemy.New(workspace.GoblinPart)\r\n```\r\n\r\n### Class adornee (framework clones it)\r\n```lua\r\nEnemy.SetAdornee(workspace.EnemyTemplate)\r\nEnemy.SetAdorneeParent(workspace.Enemies) -- optional, defaults to workspace\r\n\r\nlocal goblin = Enemy.New() -- clones EnemyTemplate automatically\r\n```\r\n\r\n### Adornee pool (random clone per instance)\r\n```lua\r\nEnemy.AddAdornee(workspace.ZombieRig1)\r\nEnemy.AddAdornee(workspace.ZombieRig2)\r\nEnemy.AddAdornee(workspace.ZombieRig3)\r\n\r\nlocal goblin = Enemy.New() -- picks a random one and clones it\r\n```\r\n\r\nYou can also mix — pass an adornee manually even when a class adornee is set:\r\n```lua\r\nif specialCase then\r\n    Enemy.New(workspace.SpecialRig) -- uses this directly\r\nelse\r\n    Enemy.New() -- uses class adornee\r\nend\r\n```\r\n\r\n---\r\n\r\n## Signals\r\n\r\nSignals are per-instance event objects. Firing one instance's signal does not affect any other instance.\r\n\r\n**Events are developer-driven. HyperEvents are engine-driven.** See [HyperSync](#hypersync-reactive-state) for the reactive counterpart.\r\n\r\n### `Class.NewSignal(name)`\r\nDefines a signal at class level. Each instance gets its own independent Signal object.\r\n```lua\r\nEnemy.NewSignal(\"Died\")\r\nEnemy.NewSignal(\"TookDamage\")\r\n```\r\n\r\n### Usage on instances\r\n```lua\r\nlocal goblin = Enemy.New(workspace.Part)\r\n\r\n-- persistent listener\r\ngoblin.Events.Died:Connect(function()\r\n    XP:Add(10)\r\nend)\r\n\r\n-- fires once then auto-disconnects\r\ngoblin.Events.TookDamage:Once(function(dmg)\r\n    print(\"first hit: \" .. dmg)\r\nend)\r\n\r\n-- yields until signal fires, returns Fire() args\r\ntask.spawn(function()\r\n    local dmg = goblin.Events.TookDamage:Wait()\r\n    print(\"waited for hit: \" .. dmg)\r\nend)\r\n\r\n-- developer fires manually inside methods\r\nEnemy.NewFunc(\"TakeDamage\", function(self, dmg)\r\n    self.Health -= dmg\r\n    self.Events.TookDamage:Fire(dmg)\r\n\r\n    if self.Health <= 0 then\r\n        self.Events.Died:Fire()\r\n    end\r\nend)\r\n```\r\n\r\n---\r\n\r\n## HyperSync (Reactive State)\r\n\r\nHyperSync automatically fires a signal when a condition becomes true after any instance property mutation.\r\nThe condition is only checked after writes, not on a loop.\r\nIt fires once per flip — resets when the condition becomes false again.\r\n\r\n### `Class.HyperSync(signalName, conditionFunc)`\r\nAuto-registers the signal too — no separate `NewSignal` needed.\r\n```lua\r\nEnemy.HyperSync(\"Died\", function(self)\r\n    return self.Health < 1\r\nend)\r\n\r\nEnemy.HyperSync(\"Critical\", function(self)\r\n    return self.Health < 25\r\nend)\r\n```\r\n\r\n### Listening to HyperSync signals\r\nHyperSync signals live on `object.HyperEvents`, separate from manual `object.Events`:\r\n```lua\r\ngoblin.HyperEvents.Died:Connect(function()\r\n    print(\"goblin died automatically!\")\r\nend)\r\n\r\ngoblin.HyperEvents.Critical:Once(function()\r\n    print(\"goblin is critical!\")\r\nend)\r\n```\r\n\r\n---\r\n\r\n## Generations\r\n\r\nInstances can spawn new instances of their own class via `self.New()`.\r\nThe framework automatically tracks generation data on every instance.\r\n\r\nGenerations are not limited to spawning enemies — they represent any parent-child runtime lineage. Use them for splitting projectiles, branching dialogue trees, chained ability effects, or anything where instances spawn related instances.\r\n\r\n| Property | What it is |\r\n|---|---|\r\n| `self.Gen` | Generation number. `1` for direct class spawns. |\r\n| `self.GenParent` | The instance that spawned this one. `nil` for gen 1. |\r\n| `self.GenChildren` | All instances this one has spawned. |\r\n| `self.GenSiblings` | Other instances spawned by the same parent. Always live. |\r\n\r\n```lua\r\n-- when a zombie dies, spawn 2 more if under gen 3\r\nZombie.SetCleanup(function(self)\r\n    if self.Gen < 3 then\r\n        self.New() -- spawns Zombie with Gen = self.Gen + 1\r\n        self.New()\r\n    end\r\nend)\r\n\r\nlocal zombie = Zombie.New(workspace.ZombiePart)\r\n-- zombie.Gen == 1\r\n-- when it dies → spawns 2x Gen 2 zombies\r\n-- when those die → spawns 2x Gen 3 zombies each\r\n-- Gen 3 zombies die → nothing spawns, chain ends\r\n```\r\n\r\n---\r\n\r\n## Instance Management\r\n\r\n### `Class.GetAll()`\r\nReturns a list of all currently active instances of this class.\r\n```lua\r\nlocal allEnemies = Enemy.GetAll()\r\nprint(#allEnemies .. \" enemies alive\")\r\n```\r\n\r\n### `Class.DestroyAll()`\r\nDestroys all active instances of this class. Safe to call mid-wave.\r\n```lua\r\nEnemy.DestroyAll() -- end of wave cleanup\r\n```\r\n\r\n### `instance:Destroy()`\r\nManually destroys a single instance.\r\n```lua\r\ngoblin:Destroy()\r\n```\r\n\r\n### `instance.IsDestroyed`\r\nBoolean flag. `true` after the instance has been destroyed.\r\n```lua\r\nif not goblin.IsDestroyed then\r\n    goblin:TakeDamage(10)\r\nend\r\n```\r\n\r\n---\r\n\r\n## Class Sealing\r\n\r\nOnce `Class.New()` is called for the first time, the class is **sealed**.\r\nAny attempt to call `NewVar`, `NewFunc`, `SetMain`, `HyperSync`, etc. after that will throw an error.\r\n\r\nThis prevents silent bugs where instances created before and after a mutation have different shapes.\r\n\r\n```lua\r\nlocal goblin = Enemy.New(workspace.Part)\r\nEnemy.NewVar(\"Speed\", 10) -- ERROR: class is sealed\r\n```\r\n\r\n---\r\n\r\n## Full Example — Zombie Wave System\r\n\r\n```lua\r\nlocal Object = require(ReplicatedStorage.Packages.Object)\r\n\r\n-- BASE CLASS\r\nlocal Enemy = Object.New(\"Enemy\")\r\nEnemy.NewVar(\"Health\", 100)\r\nEnemy.NewVar(\"MaxHealth\", 100, \"immutable\")\r\nEnemy.NewSignal(\"TookDamage\")\r\nEnemy.HyperSync(\"Died\", function(self) return self.Health < 1 end)\r\n\r\nEnemy.NewFunc(\"TakeDamage\", function(self, dmg)\r\n    self.Health -= dmg\r\n    self.Events.TookDamage:Fire(dmg)\r\nend)\r\n\r\nEnemy.SetMain(function(self)\r\n    print(self.Adornee.Name .. \" spawned\")\r\nend)\r\n\r\n-- ZOMBIE (extends Enemy)\r\nlocal Zombie = Enemy:Extend(\"Zombie\")\r\nZombie.NewVar(\"InfectionChance\", 0.3)\r\nZombie.AddAdornee(workspace.ZombieRig1)\r\nZombie.AddAdornee(workspace.ZombieRig2)\r\nZombie.SetAdorneeParent(workspace.Enemies)\r\n\r\n-- split into 2 on death if under gen 3\r\nZombie.SetCleanup(function(self)\r\n    if self.Gen < 3 then\r\n        self.New()\r\n        self.New()\r\n    end\r\nend)\r\n\r\n-- FAST ZOMBIE (extends Zombie)\r\nlocal FastZombie = Zombie:Extend(\"FastZombie\")\r\nFastZombie.NewVar(\"Speed\", 30)\r\n\r\n-- skips Zombie's TakeDamage, uses Enemy's directly\r\nFastZombie.NewHyperFunc(\"TakeDamage\", \"Enemy\", function(self, hyper, dmg)\r\n    hyper()\r\nend)\r\n\r\n-- SPAWN A WAVE\r\nfor i = 1, 10 do\r\n    local zombie = Zombie.New()\r\n    zombie.HyperEvents.Died:Connect(function()\r\n        print(\"zombie died at gen \" .. zombie.Gen)\r\n    end)\r\nend\r\n\r\n-- END OF WAVE\r\ntask.wait(60)\r\nZombie.DestroyAll()\r\nFastZombie.DestroyAll()\r\n```\r\n\r\n---\r\n\r\n## License\r\nMIT\r\n","readmeTruncated":false}