This commit is contained in:
NoryiE
2025-02-16 19:31:38 +00:00
parent dd6825f3b6
commit 056897dd1b
36 changed files with 1085 additions and 5065 deletions

View File

@@ -1,383 +1,53 @@
local deepCopy = require("libraries/utils").deepCopy
local expect = require("libraries/expect")
local errorManager = require("errorManager")
local log = require("log")
# PropertySystem
--- @class PropertySystem
local PropertySystem = {}
PropertySystem.__index = PropertySystem
## Functions
PropertySystem._properties = {}
local blueprintTemplates = {}
|Method|Returns|Description|
|---|---|---|
|[PropertySystem.addSetterHook](#PropertySystem.addSetterHook)|-|
|[PropertySystem.blueprint](#PropertySystem.blueprint)|table|
|[PropertySystem.combineProperties](#PropertySystem.combineProperties)|-|
|[PropertySystem.createFromBlueprint](#PropertySystem.createFromBlueprint)|-|
|[PropertySystem.defineProperty](#PropertySystem.defineProperty)|-|
|[PropertySystem:__init](#PropertySystem:__init)|-|
|[PropertySystem:_updateProperty](#PropertySystem:_updateProperty)|-|
|[PropertySystem:getPropertyConfig](#PropertySystem:getPropertyConfig)|-|
|[PropertySystem:instanceProperty](#PropertySystem:instanceProperty)|-|
|[PropertySystem:observe](#PropertySystem:observe)|-|
|[PropertySystem:removeAllObservers](#PropertySystem:removeAllObservers)|-|
|[PropertySystem:removeObserver](#PropertySystem:removeObserver)|-|
|[PropertySystem:removeProperty](#PropertySystem:removeProperty)|-|
PropertySystem._setterHooks = {}
## PropertySystem.addSetterHook()
function PropertySystem.addSetterHook(hook)
table.insert(PropertySystem._setterHooks, hook)
end
## PropertySystem.blueprint(elementClass)
Creates a blueprint of an element class with all its properties
local function applyHooks(element, propertyName, value, config)
for _, hook in ipairs(PropertySystem._setterHooks) do
local newValue = hook(element, propertyName, value, config)
if newValue ~= nil then
value = newValue
end
end
return value
end
### Parameters
* `elementClass` `table` The element class to create a blueprint from
function PropertySystem.defineProperty(class, name, config)
if not rawget(class, '_properties') then
class._properties = {}
end
### Returns
* `table` `blueprint` A table containing all property definitions
class._properties[name] = {
type = config.type,
default = config.default,
canTriggerRender = config.canTriggerRender,
getter = config.getter,
setter = config.setter,
}
## PropertySystem.combineProperties()
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
## PropertySystem.createFromBlueprint()
class["get" .. capitalizedName] = function(self, ...)
expect(1, self, "element")
local value = self._values[name]
if type(value) == "function" and config.type ~= "function" then
value = value(self)
end
return config.getter and config.getter(self, value, ...) or value
end
## PropertySystem.defineProperty()
class["set" .. capitalizedName] = function(self, value, ...)
expect(1, self, "element")
value = applyHooks(self, name, value, config)
## PropertySystem:__init()
if type(value) ~= "function" then
expect(2, value, config.type)
end
## PropertySystem:_updateProperty()
if config.setter then
value = config.setter(self, value, ...)
end
## PropertySystem:getPropertyConfig()
self:_updateProperty(name, value)
return self
end
end
## PropertySystem:instanceProperty()
function PropertySystem.combineProperties(class, name, ...)
local properties = {...}
for k,v in pairs(properties)do
if not class._properties[v] then errorManager.error("Property not found: "..v) end
end
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
## PropertySystem:observe()
class["get" .. capitalizedName] = function(self, ...)
expect(1, self, "element")
local value = {}
for _,v in pairs(properties)do
value[v] = self.get(v)
end
return table.unpack(value)
end
## PropertySystem:removeAllObservers()
class["set" .. capitalizedName] = function(self, ...)
expect(1, self, "element")
local values = {...}
for i,v in pairs(properties)do
self.set(v, values[i])
end
return self
end
end
## PropertySystem:removeObserver()
--- Creates a blueprint of an element class with all its properties
--- @param elementClass table The element class to create a blueprint from
--- @return table blueprint A table containing all property definitions
function PropertySystem.blueprint(elementClass, properties, basalt, parent)
if not blueprintTemplates[elementClass] then
local template = {
basalt = basalt,
__isBlueprint = true,
_values = properties or {},
_events = {},
render = function() end,
dispatchEvent = function() end,
init = function() end,
}
## PropertySystem:removeProperty()
template.loaded = function(self, callback)
self.loadedCallback = callback
return template
end
template.create = function(self)
local element = elementClass.new()
element:init({}, self.basalt)
for name, value in pairs(self._values) do
element._values[name] = value
end
for name, callbacks in pairs(self._events) do
for _, callback in ipairs(callbacks) do
element[name](element, callback)
end
end
if(parent~=nil)then
parent:addChild(element)
end
element:updateRender()
self.loadedCallback(element)
element:postInit()
return element
end
local currentClass = elementClass
while currentClass do
if rawget(currentClass, '_properties') then
for name, config in pairs(currentClass._properties) do
if type(config.default) == "table" then
template._values[name] = deepCopy(config.default)
else
template._values[name] = config.default
end
end
end
currentClass = getmetatable(currentClass) and rawget(getmetatable(currentClass), '__index')
end
blueprintTemplates[elementClass] = template
end
local blueprint = {
_values = {},
_events = {},
loadedCallback = function() end,
}
blueprint.get = function(name)
local value = blueprint._values[name]
local config = elementClass._properties[name]
if type(value) == "function" and config.type ~= "function" then
value = value(blueprint)
end
return value
end
blueprint.set = function(name, value)
blueprint._values[name] = value
return blueprint
end
setmetatable(blueprint, {
__index = function(self, k)
if k:match("^on%u") then
return function(_, callback)
self._events[k] = self._events[k] or {}
table.insert(self._events[k], callback)
return self
end
end
if k:match("^get%u") then
local propName = k:sub(4,4):lower() .. k:sub(5)
return function()
return self._values[propName]
end
end
if k:match("^set%u") then
local propName = k:sub(4,4):lower() .. k:sub(5)
return function(_, value)
self._values[propName] = value
return self
end
end
return blueprintTemplates[elementClass][k]
end
})
return blueprint
end
function PropertySystem.createFromBlueprint(elementClass, blueprint, basalt)
local element = elementClass.new({}, basalt)
for name, value in pairs(blueprint._values) do
if type(value) == "table" then
element._values[name] = deepCopy(value)
else
element._values[name] = value
end
end
return element
end
function PropertySystem:__init()
self._values = {}
self._observers = {}
self.set = function(name, value, ...)
local oldValue = self._values[name]
local config = self._properties[name]
if(config~=nil)then
if(config.setter) then
value = config.setter(self, value, ...)
end
if config.canTriggerRender then
self:updateRender()
end
self._values[name] = applyHooks(self, name, value, config)
if oldValue ~= value and self._observers[name] then
for _, callback in ipairs(self._observers[name]) do
callback(self, value, oldValue)
end
end
end
end
self.get = function(name, ...)
local value = self._values[name]
local config = self._properties[name]
if(config==nil)then errorManager.error("Property not found: "..name) return end
if type(value) == "function" and config.type ~= "function" then
value = value(self)
end
return config.getter and config.getter(self, value, ...) or value
end
local properties = {}
local currentClass = getmetatable(self).__index
while currentClass do
if rawget(currentClass, '_properties') then
for name, config in pairs(currentClass._properties) do
if not properties[name] then
properties[name] = config
end
end
end
currentClass = getmetatable(currentClass) and rawget(getmetatable(currentClass), '__index')
end
self._properties = properties
local originalMT = getmetatable(self)
local originalIndex = originalMT.__index
setmetatable(self, {
__index = function(t, k)
local config = self._properties[k]
if config then
local value = self._values[k]
if type(value) == "function" and config.type ~= "function" then
value = value(self)
end
return value
end
if type(originalIndex) == "function" then
return originalIndex(t, k)
else
return originalIndex[k]
end
end,
__newindex = function(t, k, v)
local config = self._properties[k]
if config then
if config.setter then
v = config.setter(self, v)
end
v = applyHooks(self, k, v, config)
self:_updateProperty(k, v)
else
rawset(t, k, v)
end
end,
__tostring = function(self)
return string.format("Object: %s (id: %s)", self._values.type, self.id)
end
})
for name, config in pairs(properties) do
if self._values[name] == nil then
if type(config.default) == "table" then
self._values[name] = deepCopy(config.default)
else
self._values[name] = config.default
end
end
end
return self
end
function PropertySystem:_updateProperty(name, value)
local oldValue = self._values[name]
if type(oldValue) == "function" then
oldValue = oldValue(self)
end
self._values[name] = value
local newValue = type(value) == "function" and value(self) or value
if oldValue ~= newValue then
if self._properties[name].canTriggerRender then
self:updateRender()
end
if self._observers[name] then
for _, callback in ipairs(self._observers[name]) do
callback(self, newValue, oldValue)
end
end
end
end
function PropertySystem:observe(name, callback)
self._observers[name] = self._observers[name] or {}
table.insert(self._observers[name], callback)
return self
end
function PropertySystem:removeObserver(name, callback)
if self._observers[name] then
for i, cb in ipairs(self._observers[name]) do
if cb == callback then
table.remove(self._observers[name], i)
if #self._observers[name] == 0 then
self._observers[name] = nil
end
break
end
end
end
return self
end
function PropertySystem:removeAllObservers(name)
if name then
self._observers[name] = nil
else
self._observers = {}
end
return self
end
function PropertySystem:instanceProperty(name, config)
PropertySystem.defineProperty(self, name, config)
self._values[name] = config.default
return self
end
function PropertySystem:removeProperty(name)
self._values[name] = nil
self._properties[name] = nil
self._observers[name] = nil
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
self["get" .. capitalizedName] = nil
self["set" .. capitalizedName] = nil
return self
end
function PropertySystem:getPropertyConfig(name)
return self._properties[name]
end
return PropertySystem