quad/src/store.lua
2023-01-04 23:59:30 +09:00

300 lines
7.4 KiB
Lua

local module = {};
--[[
this feature allows get object without making some created callback and local var
but, it can't allows mulit id and object
this feature should be upgraded
]]
local wrap = coroutine.wrap;
local insert = table.insert;
local remove = table.remove;
local gmatch = string.gmatch;
local gsub = string.gsub;
local function catch(...)
local passed,err = pcall(...);
if not passed then
warn ("[QUAD] Error occured while operating async task\n" .. tostring(err));
end
end
local week = {__mode = "v"};
---@param shared quad_export
---@return quad_module_store
function module.init(shared)
---@class quad_module_store
local new = {__type = "quad_module_store"};
local items = shared.items;
-- id space (array of object)
local objectListClass = {__type = "quad_objectlist"};
function objectListClass:each(func)
local index = 1;
for i,v in pairs(self) do
wrap(catch)(func,index,v);
index = index + 1;
end
end
function objectListClass:eachSync(func)
local index = 1;
for _,v in pairs(self) do
local ret = func(index,v);
index = index + 1;
if ret then
break;
end
end
end
function objectListClass:remove(indexOrItem)
local thisType = type(indexOrItem);
if thisType == "number" then
remove(self,indexOrItem);
else
for i,v in pairs(self) do
if v == indexOrItem then
remove(self,i);
break;
end
end
end
end
function objectListClass:isEmpty()
if next(self) then return true; end
return false;
end
function objectListClass.__new()
return setmetatable({},objectListClass);
end
function objectListClass:__newIndex(key,value) -- props setter
self:each(function (this)
this[key] = value;
end);
end
objectListClass.__mode = "kv"; -- week link for gc
objectListClass.__index = objectListClass;
-- get object array with id (objSpace)
function new.getObjects(id)
local list = items[id];
if list then return list; end
list = objectListClass.__new(id);
items[id] = list;
return list;
end
-- get first object with id (not array)
function new.getObject(id)
local item = items[id];
return item and item[next(item)];
end
--TODO: if item is exist already, ignore this call
-- adding object with id
function new.addObject(ids,object)
for id in gmatch(ids,"[^,]+") do -- split by ,
-- remove trailing, heading spaces
id = gsub(gsub(id,"^ +","")," +$","");
local array = items[id];
if not array then
array = objectListClass.__new();
items[id] = array;
end
insert(array, object);
end
end
local registerClass = {
__type = "quad_register";
register = function (s,efunc)
local self = s.store;
local events = self.__evt;
for key in s.key:gmatch("[^,]+") do
key = key:gsub("^ +",""):gsub(" +$","");
local event = events[key];
if not event then
event = setmetatable({},week);
events[key] = event;
end
insert(event,efunc);
end
end;
with = function (s,wfunc)
-- s.wfunc = wfunc;
-- return s;
return setmetatable({wfunc = wfunc},{__index = s});
end;
default = function (s,dvalue)
-- s.dvalue = dvalue;
-- return s;
return setmetatable({dvalue = dvalue},{__index = s});
end;
tween = function (s,tvalue)
-- s.tvalue = tvalue;
-- return s;
return setmetatable({tvalue = tvalue},{__index = s});
end;
---@deprecated
from = function (s,fvalue)
warn "[QUAD] register:from() is deprecated. You can use register:add(t:table|function) instead";
-- s.fvalue = fvalue;
-- return s;
return setmetatable({fvalue = fvalue},{__index = s});
end;
add = function (s,avalue)
-- s.avalue = avalue;
-- return s;
return setmetatable({avalue = avalue},{__index = s});
end;
-- return init data
calcWithDefault = function (s,withItem)
local with = s.wfunc;
local tstore = s.store;
local rawKey = s.key;
local tween = s.tvalue or s.dtvalue;
local from = s.fvalue;
local add = s.avalue;
local set = tstore[rawKey];
if set ~= nil or (rawKey:match(",") and with) then
if add then
set = set + add;
end
if from then
set = from[set];
end
if with then
set = with(tstore,set,rawKey,withItem);
end
return set,tween;
else
local dset = s.dvalue;
if dset ~= nil then
return dset,tween;
end
warn "[Quad] no default value found.";
end
end;
calcWithNewValue = function(s,withItem,newValue,key)
local with = s.wfunc;
local tstore = s.store;
local tween = s.tvalue or s.dtvalue;
local from = s.fvalue;
local add = s.avalue;
if add then
newValue = newValue + add;
end
if from then
newValue = from[newValue];
end
if with then
newValue = with(tstore,newValue,key,withItem);
end
return newValue,tween;
end
};
registerClass.__index = registerClass;
-- bindable store object
local store = {__type = "quad_store"};
local storeIdSpace = {};
function store:__index(key)
local this = self.__self[key];
if this ~= nil then
return this;
end
return store[key];
end
function store:__newindex(key,value)
-- if got register, just copy data to self and connect
if type(value) == "table" and value.__type == "quad_register" then
warn "[Quad] adding register value on store is only allowed when init store. set value request was ignored";
return;
end
self.__self[key] = value;
local event = self.__evt[key];
if event then
for _,v in pairs(event) do -- NO ipairs here
wrap(catch)(v,store,value,key);
end
end
end
-- init bindings
function new.__initStoreRegisterBinding(self,withItem)
local selfTweens = self.__tweens;
local selfValues = self.__self;
for key,item in pairs(selfValues) do
if type(item) == "table" and item.__type == "quad_register" then
-- fetch data from origin
do
local tstore = item.store;
for tkey in item.key:gmatch("^[,]") do
selfValues[tkey] = tstore[tkey];
end
end
-- calc value
do
local setValue,tween = item:calcWithDefault(withItem);
selfValues[key] = setValue;
selfTweens[key] = tween;
end
-- make event connection
item:register(function (_,newValue,eventKey)
-- !HOLD IT SELF TO PREVENT THIS REGISTER BEGIN REMOVED FROM MEMORY
local setValue = item:calcWithNewValue(withItem,newValue,eventKey);
self[key] = setValue;
end);
end
end
end
-- create register
function store:__call(key,func)
local last = self.__self[key];
if last and type(last) == "table" and getmetatable(last) == registerClass then
return last;
end
local register = self.__reg[key];
local tweens = self.__tweens;
if not register then
register = setmetatable({
key = key;
store = self;
dtvalue = tweens and tweens[key];
},registerClass);
self.__reg[key] = register;
end
if func then
register.register(func);
end
return register;
end
function store:default(key,value)
local old = self[key];
if old == nil then
self[key] = value;
return;
end
end
function new.new(self,id)
local this = setmetatable(
{
__self = self or {}, -- real value storage
__evt = {}, -- changed event handler functions (dict of array)
__reg = setmetatable({},week), -- save registers
__tweens = {} -- tweens (if inited with register, save tween and use as default tween data)
},store
);
if id then -- save in id space
storeIdSpace[id] = this;
end
return this;
end
local storeNew = new.new;
function new.getStore(id)
return storeIdSpace[id] or storeNew({},id);
end
return new;
end
return module;