mayron / mayronobjects Goto Github PK
View Code? Open in Web Editor NEWAn OOP Lua framework and packaging ecosystem for developing well-defined packages with strict typing support.
Home Page: https://mayronui.com/p/mayron-objects
An OOP Lua framework and packaging ecosystem for developing well-defined packages with strict typing support.
Home Page: https://mayronui.com/p/mayron-objects
I'm curious if there'd be any value in the List
class having some copy utility? Sometimes I'd rather retain a copy of data (when it won't impact performance) than retain a reference to a list that may have been passed in as a function argument. I have some quick and dirty boilerplate that I use (admittedly only in 2 places ha), but thought the library might benefit from built-in support.
First off, as an embedded C/C++ dev who has only just been getting into Lua for fun, this library has been a blessing! I've mostly been using it for my own library addon to provide enhancements and reusable code for other addons like WeakAuras and such. However, I was drafting a design to take advantage of Interfaces, but also realized that there are a few things I couldn't do with it. I wanted to create an abstract base class that implemented an interface to provide things like common base constructor. Below is an example of what I have now (sorry for the wall of code!)
-- Assumes package is already defined.
local IFoo = package:CreateInterface("IFoo", {
GetText = {type = "function", returns = "string"},
GetNumber = {type = "function", returns = "number"}
Print = {type = "function" }
}
--[[
Intended to be an interface "default" function. Maybe I did something wrong,
but I was unable to do something like:
function IFoo:Print(data) print(self:GetText() .. self:GetNumber()) end
--]]
function IFoo.Print(obj)
print(obj:GetText() .. obj:GetNumber())
end
-- "abstract" base class and constructor definition
local CAbstractFoo = package:CreateClass("CAbstractFoo", nil, IFoo)
package:DefineParams("number")
function CAbstractFoo:__Construct(data, number)
data.number = number
self:CheckNotAbstract()
end
-- A function used to somewhat enforce the abstract nature of the class.
package:DefineVirtual()
function CAbstractFoo:CheckNotAbstract()
error("This class is abstract and should not be instantiated!")
end
-- Base implementation to return common state
package:DefineReturns("number")
function CAbstractFoo:GetNumber(data)
return data.number
end
-- "abstract" method that I would have left out in the base class were it
-- possible
package:DefineVirtual()
package:DefineReturns("string")
function CAbstractFoo:GetText()
error("This function is abstract, and should not be called!")
end
-- Print implementation using the interface default method.
function CAbstractFoo:Print()
IFoo.Print(self)
end
-- Concrete class and constructor definition
local CConcreteFoo = package:CreateClass("CConcreteFoo", CAbstractFoo)
package:DefineParams("string", "number")
function CConcreteFoo:__Construct(data, text, number)
self:Super(number)
data.number = number
end
-- Implementation to shadow base class to avoid "abstract" error.
function CConcreteFoo:CheckNotAbstract()
-- Do nothing dummy function
end
-- Implementation to shadow base class to avoid "abstract" error.
package:DefineReturns("string")
function CConcreteFoo:GetText(data)
return data.text
end
There ends up being a lot of boilerplate code for things that could be implemented as interface default methods. If the library supported it, maybe the code in my example would look something like this?
-- Assumes package is already defined.
local IFoo = package:CreateInterface("IFoo", {
GetText = {type = "function", returns = "string"},
GetNumber = {type = "function", returns = "number"}
Print = {type = "function" }
}
--[[
Something like this could be used to define an interface method that doesn't
know about any object state other than that defined by the interface itself.
--]]
package:DefineDefault()
function IFoo:Print()
print(self:GetText() .. self:GetNumber())
--[[
Behavior like these should result in an error however. When the scope of
self is the interface, giving access to methods from child classes
amounts to reflection. Also, allowing direct modification of an object's
properties that aren't exposed by the interface's definition would be
asking for coupling issues galore in client code down the road.
I've seen some gross things done in C++ with stateful pure virtual
classes and it makes me wince ๐
.
if self:GetName then print(self:GetName()) end
self.sideEffect = true
--]]
end
--[[
Something like this could be used to mark a class as abstract, and if that
class implements any interfaces, the framework would not treat missing
interface functions as errors. Called right before the creation of the class,
it would accept a table of abstract function definitions that must also be
implemented by non-abstract children in addition to any unimplemented
interfaces.
--]]
package:DefineAbstract({
GetName = {returns = "string"}
DoubleNumberAndAdd = {params = "number", returns ="number" }
}
--[[
Something like this could be used to ensure that only classes in the same
package can declare the protected class as a parent. For example, calling
CreateClass("CConcreteFoo2", CAbstractFoo) from a different package would
result in an error from the framework.
--]]
package:DefinePackageVisibility()
--[[
Create the abstract class, and no abstract instantiation check is required
by client code. Attempting to call CAbstractFoo(0) would result in an error
from the framework.
--]]
local CAbstractFoo = package:CreateClass("CAbstractFoo", nil, IFoo)
package:DefineParams("number")
function CAbstractFoo:__Construct(data, number)
data.number = number
end
--[[
Something like this could be allowed to provide a default implementation for
DoubleNumberAndAdd, but still require that children implement it. Children may
call it explicitly like any other parent method however.
--]]
function CAbstractFoo:DoubleNumberAndAdd(number)
return (self:GetNumber() * 2) + number
end
--[[
Something like this would be used to override the default implementation of
the interface, giving more visibility into the object's state. I've used
self.Interfaces as an example of how it might be accessed (similar to calling)
parent methods.
--]]
function CAbstractFoo:Print(data)
self.Interfaces:Print()
print(self:GetName())
end
-- Base implementation to return common state
package:DefineReturns("number")
function CAbstractFoo:GetNumber(data)
return data.number
end
--[[
Something like this could be the mechanism for defining protected functions.
Just like functions defined on the Private table, they would not be visible
outside the scope of the current class, with the exception that child classes
would be able to call them using data:Call(). These methods could also be
allowed to have package:DefinesVirtual() to allow a child to override the
method.
--]]
package:DefineReturns("number")
function CAbstractFoo.Protected:GetHalfNumber()
return self:GetNumber() / 2
end
-- Concrete class definition
local CConcreteFoo = package:CreateClass("CConcreteFoo", CAbstractFoo)
local fooInstance = CFooConcrete("test", 0)
-- Concrete class constructor
package:DefineParams("string", "number")
function CConcreteFoo:__Construct(data, text, number)
self:Super(number)
data.text = text
end
-- GetText required implementation from interface
function CConcreteFoo:GetText(data)
return data.text
end
-- GetName required implementation from abstract class
function CConcreteFoo:GetName(data)
return "childUsingProtected:" .. data:Call("GetHalfNumber")
end
I'm still getting the hang of Lua in general, so hopefully things make sense!
Hey man,
Great job on the library. I am liking what I am seeing, only If I could get the project started that would be great.
I am trying to build an addon for my guild. I had a small addon back in the day, when retail was a big thing (around WOTLK). But since I have fallen out of favor with Lua. I mostly dabble in TypeScript and C# now.
I am trying to get an addon setup. But I am getting errors.
I can't seem to import LibMayronObjects and LibMayronDB.
I changed the Interface Version in both .toc files (as you said in your guides) to 11304 (thats what I get by running /run print((select(4, GetBuildInfo()))); in WoW Classic).
But I am getting the following error from WoW Classic:
Message: Interface\AddOns\HarmonyAntiDC\Library\LibMayronDB\LibMayronDB.toc(1): error: not well-formed (invalid token)
Time: Thu Apr 2 18:14:35 2020
Count: 3
and
Message: (null)
Time: Thu Apr 2 18:14:35 2020
Count: 3
I tried to use one of your "test" classes in my plugin .lua. But as I said I get the error up top.
Any way you can help me get everything setup so I can try out the framework?
My plugintest.toc:
##Interface: 11304
##Title: plugintest
##Author: steviss
##Notes: Test addon.
##Version: 1.0
plugintest.lua
#Myron Objects
Library\LibMayronObjects\LibMayronObjects.lua
Library\LibMayronObjects\LibMayronObjects.xml
Library\LibMayronObjects\LibStub.lua
Library\LibMayronObjects\Attributes\InCombatAttribute.lua
Library\LibMayronObjects\Objects\FrameWrapper.lua
Library\LibMayronObjects\Objects\LinkedList.lua
Library\LibMayronObjects\Objects\List.lua
Library\LibMayronObjects\Objects\Map.lua
Library\LibMayronObjects\Objects\Stack.lua
# Myron DB
Library\LibMayronDB\LibMayronDB.lua
Library\LibMayronDB\LibMayronDB.toc
Library\LibMayronDB\LibMayronObjects.lua
Library\LibMayronDB\LibStub.lua
My plugintest.lua file:
-- luacheck: ignore self 143 631
local lib = _G.LibStub:GetLibrary("LibMayronObjects");
local function HelloWorld_Test1() -- luacheck: ignore
print("HelloWorld_Test1 Started");
local TestPackage = lib:CreatePackage("HelloWorld_Test1", "Test");
local HelloWorld = TestPackage:CreateClass("HelloWorld");
function HelloWorld:Print(private, msg)
assert(msg == "My 2nd Message");
assert(private.secret == "This is a secret!");
end
function HelloWorld:__Construct(private, msg)
private.secret = "This is a secret!";
assert(msg == "My 1st Message");
assert(self ~= HelloWorld);
end
function HelloWorld:__Destruct()
--print("Instance Destroyed"); -- works!
end
local instance = HelloWorld("My 1st Message");
instance:Print("My 2nd Message");
local HelloWorld2 = lib:Import("Test.HelloWorld_Test1.HelloWorld");
assert(HelloWorld == HelloWorld2);
local className = instance:GetObjectType();
assert(className == "HelloWorld", className);
instance:Destroy();
print("HelloWorld_Test1 Successful!");
end
If you can help me get started that would be great.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.