Classe v1.2.2 - highly typed, Simple, Fast OOP Constructor


creatorStore

github

Latest Update

:bookmark: What is Classe?

Classe is a highly typed module designed to solve issues related to class organization, typing, and performance in Luau.
It automatically infers object types, including inheritance cases, eliminating the need to manually define types or reuse metatable class templates.

Classe also applies performance-oriented approaches by avoiding __index chains.

:warning: Requirements

To work correctly, you must enable the Beta Luau Type Solver in Roblox Studio settings

Autocomplete demonstration

Basic Class

local Classe = require(script.Parent.Classe)

local Weapon = Classe.meta({} :: {
	bullets: number

}, {})

type Self = Classe.Self<typeof(Weapon)>
type ISelf = Classe.ISelf<typeof(Weapon)>

function Weapon.shot(self: Self)
	self.bullets -= 1
	print("Boom!")
end

function Weapon.construct(self: ISelf)
	self.bullets = 10
end

return Classe.build(Weapon)

Inheritance

local Classe = require(script.Parent.Classe)
local Weapon = require(script.Parent.Weapon)

local Rifle, super = Classe.meta({} :: {
	isAiming: boolean

}, {}, Weapon)

type Self = Classe.Self<typeof(Rifle)>
type ISelf = Classe.ISelf<typeof(Rifle)>

function Rifle.aim(self: Self)
	self.isAiming = true
	print("aiming...")
end

--[[ 
also use the init key function, 
which will be called after construct 
]]
function Rifle.init(self: Self)
	print("bullets:", self.bullets)
end

function Rifle.construct(self: ISelf)
	super(self)
	self.isAiming = false
end

return Classe.build(Rifle)

:stopwatch: Speed and memory

Classe is primarily focused on performance rather than memory efficiency.

During inheritance, the parent’s metadata is copied into the child. This should not significantly impact memory usage, since mostly function references and primitive values are copied, which are inexpensive on their own.

This approach improves method call performance because it avoids __index chains and additional lookups across the inheritance hierarchy.

If a class has subclasses and its metadata needs to be modified, __newindex is triggered. It iterates through all child classes and updates the corresponding data, while checking whether the metadata has been overridden in the subclass.


Here is a method call at the 99th level of inheritance (500k tests)

9 Likes

Next Update:

  • The ability to change the metadata of an already built class
  • make types more secure

looks very solid. this module Is very useful in UI components maybe

Release Update v1.0.0

  • The ability to change the metadata of an already built class
  • New type ISelf<M> for the self parameter of the init construct method
  • Minor changes in typing

Probably going to use this, but can you add a benchmark, people are probably going to ask for it and it’ll make the resource more attractive

1 Like

it looks like __index is faster than my method. With 100,000 tests per 100 inheritances, the __index was 30% faster or 1% slower. I don’t quite understand how luau optimizes all this. If this is not the case and I do not understand something, then tell me

this might help

3 Likes

Next Update:

Release Update v1.1.0 (goodbye phtd)

  • Now you no longer need to manually work with PhantomData - Classe handles everything automatically. All data is now stored within the meta’s metadata while preserving scalability

private and public fields will not be implemented due to current limitations of Beta Luau Type Solver

Release Update v1.2.0

  • Added a new lifecycle method - construct.
    construct is responsible for creating and initializing the object’s internal state (formerly handled by init)
    The init method is now executed after instance creation, in inheritance order (from the oldest ancestor to the most derived class)

  • Improved overall performance

  • Fixed immutability guarantees

3 Likes

Release Update v1.2.1

  • slightly improved performance and bug fixed

Sorry, but I couldn’t use Classe due to the beta type solver freezing my studio while loading my big project and blowing up many memory.. Is it still usable without it?

Yes, but without the Beta Luau Type Solver, type functions won’t work and will be marked as unknown syntax. All types will become errors

1 Like

Ok so it might work with enabling script non-strict but the next day got me loaded in the project.. also how do you add parameters/options to the new function?
edit: it was by the construct function. gonna use this!! thanks!!!

Weird enough it seems like one of the module type like Val doesn’t seem to work with autocomplete, but sometimes it worked when i was writing the attribute?

just the type if you were curious: export type Val<T> = typeof(setmetatable({} :: ValInternal<T>, {} :: typeof(Val)))

You need to use the key function init

function MyClass.init(self: Self)
     -- this will be called automatically after the class is built
     -- you can do:
     self.state:Something()
     self:Something()
end

Release Update v1.2.2

  • fixed incorrect overriding logic

Im not sure how your Classe works, but init seems to be for when the class is built.. Im doing construct just because I need multiple objects to be created. In that case it doesn’t help me because it does the same problem, but finishing the key call manually gives me parameters preview

i apologize if im asking too much XD but also, most of my classes doesn’t show the parameters..

code:

--!strict
local Parent = script.Parent.Parent.Parent
local Classe = require(Parent.Misc.Classe)
local Val = require(Parent.Misc.Val)
local Types = require(Parent.Types)

local ObjectiveData = Classe.meta({}::Types.IObjectiveDataInstance,{})

export type Self = Classe.Self<typeof(ObjectiveData)>
type ISelf = Classe.ISelf<typeof(ObjectiveData)>

function ObjectiveData.construct(self:Self,ref:Types.IObjectiveData)
	
	self.ref = ref

	self._map = {}

end

function ObjectiveData.set(self:Self,key:string,value:any)
	
	self._map[key] = self._map[key] or Val.new(value)
	self._map[key]:set(value)
	
end

function ObjectiveData.getVal(self:Self,key:string) : Val.Val<any>

	--assert(self._map[key],`{key} not found within the data map!!`)
	
	return self._map[key]

end

function ObjectiveData.get(self:Self,key:string) : any
	local val = self:getVal(key)
	if not val then return end
	return val:get()

end

function ObjectiveData.serialize(self:Self) : {[string]:any}
	
	local data = {}
	
	for k,v in self._map do
		
		data[k] = self:get(k)
		
	end
	
	return data
	
end

return Classe.build(ObjectiveData)

Как всегда фигня. Переделывай, брат.

i would like an elaboration instead of just responding without any guidance🤨