#GitHub - noaccessl/glua-RobustClass: A f...
1 messages · Page 1 of 1 (latest)
why not an object based approach aka class(classType, classTypeParent) instead of robustclass( 'B : A' )
More for variety reasons and it kind of reflects of what's going on
iunno i did it the other way in my impl
Somehow I'm finding your example code to be difficult to follow
It's early in the morning, admittedly
I think using easy-to-forget names like A and B makes it harder to follow than if you called them something like Dog : Pet, Cat : Pet, Pet : Animal, Animal
500 lines class lib 0_o
Multiple inheritance đź’€
Well well well
If it isn't just the thing I need to port some C++ over to Lua
@broken bluff Please consider including Lua Language Server annotations so that the documentation can be read directly in-editor rather than as a separate document
What do you mean by that?
This is a C++ like class system which will be helpful in recreating some C++ classes in Lua
usage example
local class = require "./class"
---@alias ECS.Entity Object<ECS.Entity.Template>
---@alias ECS.Entity.Proto Object<ECS.Entity.Template>
---@class ECS.Entity.Template
---@field components table<ECS.Component.Proto, ECS.Component>
---@field realm ECS.Realm
local entityProto = { }
function entityProto:init()
self.components = { }
return self
end
-- Associate this entity with a realm
---@param realm ECS.Realm
function entityProto:associate(realm)
assert(not self.realm, "This entity is already associated with a realm")
self.realm = realm
end
---@param ... ECS.Component
---@protected
function entityProto:update(...)
self.realm.queryManager:update(self, ...)
end
-- Always try to add as much components per call as you can. This reduces the overhead required for persistent query rematching
---@param ... ECS.Component
function entityProto:add(...)
for i = 1, select("#", ...) do
---@type ECS.Component
local comp = select(i, ...)
assert(not self.components[comp.class], "Trying to add existing component")
self.components[comp.class] = comp
end
-- if we are not assigned to one yet, dont bother, this will run on assignation
if self.realm then
self:update(...)
end
end
---@param component ECS.Component
function entityProto:remove(component)
self.components[component.class] = nil
self:update(component)
end
return class.make(entityProto, "ECS.Entity")
@broken bluff One issue I'm running into is that there is no support for namespaces. If I have a class called "Rect", I can't easily protect it from name collisions with any other addon which might use the same name
I can't name the class "namespace.Rect" because constructors must share their name with their class
So I'm forced to do namespace_Rect which isn't very nice to use
If I could name the class namespace.Rect and have it still understand that Rect is the name of the constructor, that would be nice
I'm also not clear on if it's easily possible to override a function from an inherited class and call the overridden base class function from within the override
Should be possible. You can do it in __new or in the head constructor. Save the original function there, override it (self.BaseClass.<funcname> = ...), use the upvalued original one within the override.
Do I get your idea properly?
Namespaces is a nice idea, you gave me something to think on
Added simple namespaces.
https://github.com/noaccessl/glua-RobustClass/commit/d350905c957825bacae26ccfe75cf2779a809622
I've run into an issue that isn't currently able to be easily solved
I'm porting some C++ code and they have a base class that automatically registers itself with a manager class in its constructor
They're using a template class in the C++ to provide an ID that the constructor will use during registration
This is a snippet of that code where CHUNKID_GAME_OBJECT_DEF_VEHICLE is the ID that is provided to the template class
SimplePersistFactoryClass<VehicleGameObjDef, CHUNKID_GAME_OBJECT_DEF_VEHICLE> _VehicleGameObjDefPersistFactory;
I'm trying to achieve that same behavior by passing the ID into the constructor but because the base class's constructor (where it registers itself using the ID) is called before the constructor of the class (where it can set the ID) the class registers itself with a nil ID
There are a few ways to solve this, of course, but a relatively easy one would be to add a PostConstructor function that gets called automatically after the constructor has finished.
This would allow me to store the ID in the constructor, then register the instance in the PostConstructor
This may be outside the scope of what this library is trying to do so I'll understand if this isn't something you want to add but it's one of the few places I've found where RobustClass doesn't solve most of my C++ class problems
@broken bluff I'm running into a problem that might be from RobustClass. When a child class inherits from two parent classes, do you expect that the fields/functions of both parent classes will be accessible through self within the child's constructor?
Yes, such is the mechanic. __index becomes metaphorically a chain of classes.
Lua allows for that
There is a subtle bug I think
I've been working to understand it for a couple of days
It's to do with inheritance
I can reproduce the error reliably in my project but that is too large so I am trying to create a smaller repro but it's difficult
If it is a bug with RobustClass, it is subtle enough to only happen in very specific circumstances
The issue is basically that when I create a new instance of my Renegade_NetworkObject class and call a function on it from the constructor, it produces a nil value error and believes the function doesn't exist
NOTE: This code does not reproduce the error and is provided for explanation
local INSTANCE = robustclass.Register( "Renegade_NetworkObject" )
function INSTANCE:Renegade_NetworkObject()
self:TestNetworkObject()
end
function INSTANCE:TestNetworkObject()
print( "TestNetworkObject" )
end
This is the specific error being produced
[renegade] addons/renegade/lua/renegade/code/wwnet/network-object.lua:90: attempt to call method 'TestNetworkObject' (a nil value)
1. ConstructorForemost - addons/renegade/lua/renegade/code/wwnet/network-object.lua:90
2. construct - addons/renegade/lua/renegade/sh_robustclass.lua:316
3. New - addons/renegade/lua/renegade/sh_robustclass.lua:399
4. unknown - addons/renegade/lua/renegade/ren_classtest.lua:33
It only seems to happen the first time that the class script executes
If changes are made and it re-executes, the problem disappears
I'm unsure why
The problem appears to be something with inherit called within robustclass.Register
Which version you're using? Have you edited anything?
Version 250217
After several days of trying to fix the problem myself, I ended up using Claude to find and fix the issue.
It seems that your method for multi-parent inheritance didn't work when many layers of parentage are required
In my case I needed it to support the following inheritance tree which it was unable to support previously:
PowerUpGameObject
└─ SimpleGameObject
└─ PhysicalGameObject
├─ DamageableGameObject
│ └─ ScriptableGameObject
│ └─ BaseGameObject
│ ├─ Persist
│ │ └─ PostLoadable
│ └─ NetworkObject
└─ CombatPhysicsObserver
└─ PhysicsObserver
Claude has perverted everything.
And 25.02.17 is quite an old date. The very first public version. I've been updating the repo ever since and fixing some stuff.
Yeah, I didn’t make a pull request because I don’t trust the quality of the code
But it did get multi inheritance working
When I’m at my PC I can try the latest version from git
Pulled the most recent version from GitHub and it produces the same kind of inheritance errors as I was seeing before
The thing with this is that your class hierarchy is bifurcated and the construct() & destruct() won't reach to the lateral subclasses. I haven't counted for this possibility when I was doing the library. I would expect the regular hierarchy to be rather linear and the side subclasses be for functionality, not additional construction. I'll have to sit with this and perhaps come up with something.
That’s why Claude made so many changes to the code
Yeah
It took it several attempts to understand that was the problem
I do like robustclass and would prefer to use human-written code so I would certainly be interested in an update if you made one
I may contribute to the documentation if I have some spare time. I think Lua will only benefit from there being a strong class system available
I do think that robustclass has one other significant missing feature: statics
Of course they can be done easily enough without a library (I do this in my project) but I think they should be a built-in feature to make it an all-in-one package
Easier to work with one system than two
If you are interested, I can share some of my code which uses robustclass so you can see how it is used by others
So an end-user «workaround» as for now would be something like try to have the tree growing in one direction, to straighten the hierarchy, so to say...
What would «statics» represent?
We use the word “flatten” for this
Oh fine
Are you familiar with statics in C++ or other languages? I can explain that if not
If that would bring clarity. Just overall in the end its create-use-destroy object.
I'll look that up
This is the Lua class I wrote to port a C++ class from the Command & Conquer: Renegade source code
The CNC.CreateExport() function creates an empty table and stores it in a cache (with its file path as the key) so other scripts can call CNC.Import( path ) to retrieve the table (CNC.Import( path ) runs include( path ) exactly once for each script and then returns the cached table for any subsequent calls)
Calling CNC.CreateExport() before CNC.Import( path ) ensures that circular dependencies (two classes which import each other) are possible.
If FirstClass imports SecondClass.lua and SecondClass imports FirstClass.lua, the order of events is:
FirstClasscallsCNC.CreateExport()and an empty table is provisioned for it.FirstClasscallsCNC.Import( "second-class.lua" )- The import/export library sees there is no cached table for
second-class.luaso it runsinclude( "second-class.lua" )SecondClasscallsCNC.CreateExport()to get its own table provisioned.SecondClassthen callsCNC.Import( "first-class.lua" )and the import/export library returns the empty table provisioned forFirstClass.SecondClasscontinues with its function and field declarations using the imported reference to theFirstClasstable where necessary.
- The import/export library returns the now populated
SecondClasstable FirstClasscontinues with its function and field declarations using the imported reference to theSecondClasstable
In this way both classes have valid references to the table that the other populates its static fields and functions in regardless of which script executes first