Skip to content

v2.1.1

Choose a tag to compare

@jaeymo jaeymo released this 27 Aug 17:51
· 21 commits to main since this release

Classy v2.1.1

This release is a substantial overhaul of Classy's internal architecture, focused on cleaner separation of responsibilities, stricter type safety, and a more extensible foundation moving forward.

Internal Architecture Overhaul

Classy's internal logic has been extensively refactored and split into dedicated modules:

  • Classy — The root module and public entry point.
  • ClassyObject — The core of Classy, including ClassyObject and AppliedObject.
  • Alias — Resolves Classy objects by tag.
  • Util — Shared utility functions.
  • Types — Shared type definitions.
  • Registry — Centralized shared state, helping eliminate circular dependencies.
  • Constructors — Contains the constructClass and constructFunction constructables.

The goal of this refactor is simple: make Classy easier to maintain and extend without sacrificing type safety.

Context and Metadata Support

Applications can now receive both a context table and metadata, allowing applied objects to be customized based on how they were created.

For example, components can be applied from a configuration table, with properties automatically passed as metadata:

local componentsList = {
	["Inventory"] = {
		MaxItemAmount = 20,
	},

	["Ammo"] = {
		UseComponent = "Resource",
		StartingAmount = 0,
		MaxCount = 0,
	},
}

someClassy.applyComponents(someInstance, componentsList)

In this example:

  • Inventory receives MaxItemAmount as metadata.
  • Ammo can build from the Resource component instead of the Ammo ClassyObject.
  • Additional properties can be supplied directly through the component configuration.

This makes component-driven systems considerably more flexible.

Example: Kill Part System

Classy supports multiple construction patterns for the same system.

--!strict

local Classy = require(path.to.classy)

local KillPartClass = {}
KillPartClass.__index = KillPartClass

export type KillPart = typeof(setmetatable({} :: {
	Part: BasePart,
	Janitor: Classy.Janitor,
}, KillPartClass))

function KillPartClass.new(part: Instance, janitor: Classy.Janitor): KillPart
	return setmetatable({
		Part = part :: BasePart,
		Janitor = janitor,
	}, KillPartClass)
end

function KillPartClass.Init(self: KillPart)
	self:_watchTouchedEvent()
end

function KillPartClass.DoSomething(self: KillPart)
	print(self)
end

function KillPartClass.Destroy(self: KillPart)
	print("This object has been destroyed!")
end

function KillPartClass._watchTouchedEvent(self: KillPart)
	self.Janitor:Add(self.Part.Touched:Connect(function(hit: BasePart)
		local humanoid = hit.Parent and hit.Parent:FindFirstChildWhichIsA("Humanoid")

		if not humanoid then
			return
		end

		humanoid.Health = 0
	end))
end

Class-Based Construction

local KillPartClassy = Classy.newClass("KillPart", KillPartClass, {
	ClassNames = { "BasePart" },
	Ancestors = { workspace },
	Logging = true,
})

Function-Based Construction

local AnotherExample = Classy.new("KillPart", function(instance: Instance, janitor: Classy.Janitor)
	return KillPartClass.new(instance, janitor)
end, {
	ClassNames = { "BasePart" },
	Ancestors = { workspace },
	Logging = true,
})

Both approaches construct the same underlying system.

KillPartClassy:Init()
AnotherExample:Init()

Once initialized, you can interact with applied objects directly:

KillPartClassy.InstanceAdded:Connect(function(instance, applied)
	applied:GetData():DoSomething()
end)