├── .gitignore ├── README.md ├── tabSimpleService.lua ├── tabSnapshotLogger.lua ├── tabSimpleClient.lua ├── tabDebugger.lua ├── tabProfiler.lua ├── tabSimpleServer.lua ├── cocosTabMachine.lua ├── tabDebuggerTrace.lua ├── tabHttp.lua ├── tabHotReload.lua ├── tabLanes.lua ├── tabClicks.lua ├── tabSerialization.lua ├── tabSocket.lua ├── tabQueue.lua ├── tabAction.lua └── cocosContext.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tabMachine 2 | My new solution to high level hierarchical multiple code flows. It aims to suport what used to be done with behavior tree or coroutine, and addresses the issues with these two solutions. 3 | 4 | A detailed introduction to 2.0 version in Chinese ([tabMachine 2.0 编程](https://docs.google.com/document/d/1Lma2r7A-3kHvZAWP7lNTLhfZoQaY2jclpIXb39P68do/edit?usp=sharing)). 5 | 6 | -------------------------------------------------------------------------------- /tabSimpleService.lua: -------------------------------------------------------------------------------- 1 | -- author cs 2 | -- email 04nycs@gmail.com 3 | -- https://github.com/ThinEureka/tabMachine 4 | -- created on Oct 18 2022 5 | local tabSocket = require("tabMachine.tabSocket") 6 | local tabSimpleService = nil 7 | 8 | tabSimpleService = _{ 9 | 10 | tabName = "tabSimpleService", 11 | 12 | s1 = function(c, socket, serviceId, serviceParams) 13 | local ip, port = socket:getpeername() 14 | c._nickName = serviceId .. "[" .. ip .. ":" .. port .. "]" 15 | c._ip = ip 16 | c._port = port 17 | c._socket = socket 18 | c._serviceId = serviceId 19 | c._tabCmdHandler = c:call(serviceParams.cmdHandler(serviceParams.debugCmd), "cmdHandler") 20 | end, 21 | 22 | s2 = function(c) 23 | c._tabSocket = c:call(tabSocket, "socket", nil, 15) 24 | c._tabSocket:acceptSocket(c._socket) 25 | c:call(c._tabSocket:tabInState(tabSocket.STATE.CONNECTED), "s3") 26 | c:call(c._tabSocket:tabPullSegments(c._tabCmdHandler.decodeSegment, function(msg) 27 | c._tabCmdHandler:onNewSegment(msg) 28 | end), "pullMsgs") 29 | end, 30 | 31 | s4 = function(c) 32 | -- for current implementation we only provide once service per tab lifetime and 33 | -- further optimization will be done only when necessarcy 34 | -- c:stop("pullMsgs") 35 | c:stop() 36 | c._isDead = true 37 | end, 38 | 39 | -- event & inner 40 | event = g_t.empty_event, 41 | 42 | inner = { 43 | tabSocket = function(c) 44 | return c._tabSocket 45 | end, 46 | 47 | tabCmdHandler = function(c) 48 | return c._tabCmdHandler 49 | end, 50 | 51 | service = function(c) 52 | return c 53 | end, 54 | }, 55 | 56 | sendRpc = function(c, ...) 57 | return c._tabCmdHandler:sendRpc(...) 58 | end, 59 | 60 | sendRequest = function(c, ...) 61 | return c._tabCmdHandler:sendRequest(...) 62 | end, 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- 67 | -- public: 68 | function tabSimpleService:isDead() 69 | return self._isDead 70 | end 71 | 72 | function tabSimpleService:reconnect(socket) 73 | return false 74 | end 75 | 76 | function tabSimpleService:getTabSocket() 77 | return self._tabSocket 78 | end 79 | 80 | function tabSimpleService:getIp() 81 | return self._ip 82 | end 83 | 84 | function tabSimpleService:getPort() 85 | return self._port 86 | end 87 | 88 | function tabSimpleService:getSocketName() 89 | return self._ip, self._port 90 | end 91 | 92 | function tabSimpleService:getServiceId() 93 | return self._serviceId 94 | end 95 | 96 | return tabSimpleService 97 | 98 | -------------------------------------------------------------------------------- /tabSnapshotLogger.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | _tabSnapshotLoggerInstance = nil 5 | tabSnapshotLogger = class("tabSnapshotLogger") 6 | 7 | 8 | local function saveErrorInfoToFile(filename, errorMsg) 9 | local file,err = io.open( filename, "wb" ) 10 | if err then return err end 11 | 12 | 13 | file:write(errorMsg) 14 | 15 | local fileSize = file:seek() 16 | file:close() 17 | return fileSize 18 | end 19 | 20 | function tabSnapshotLogger:getInstance() 21 | if _tabSnapshotLoggerInstance == nil then 22 | _tabSnapshotLoggerInstance = tabSnapshotLogger.new() 23 | end 24 | return _tabSnapshotLoggerInstance 25 | end 26 | 27 | function tabSnapshotLogger:ctor() 28 | self.reportPool = {} 29 | end 30 | 31 | 32 | function tabSnapshotLogger:dumpTabSnapshot( errorMsg, errorStack, tabStack ) 33 | local md5Key = CS.Utils.GetMD5(errorMsg .. errorStack) 34 | local info = self.reportPool[md5Key] 35 | if info then 36 | local count = info.count 37 | local frameIndex = info.frameIndex 38 | info.count = count + 1 39 | info.frameIndex = g_frameIndex 40 | if count > 20 or frameIndex >= g_frameIndex - 1 then 41 | print("tabSnapshotLogger:dumpTabSnapshot 频繁触发,暂时忽略") 42 | return 43 | end 44 | else 45 | info = { 46 | count = 1, 47 | frameIndex = g_frameIndex 48 | } 49 | self.reportPool[md5Key] = info 50 | end 51 | 52 | 53 | local tabSerialization = require("tabMachine.tabSerialization") 54 | local rootContext = g_root 55 | 56 | local snapshotName = md5Key .. os.date('_%Y-%m-%d-%H-%M-%S_') .. g_frameIndex 57 | local filename = CS.UnityEngine.Application.persistentDataPath .. "/err_snapshots/" .. snapshotName 58 | print("creating snapshot ", filename) 59 | 60 | CS.SystemIOUtils.EnsureFileDirectoryExists(filename) 61 | 62 | local socket = require("socket") 63 | local t0 = socket.gettime() 64 | local detailControl = { 65 | addPath = false, 66 | -- addStat = { 67 | -- statTreeSize = true, 68 | -- }, 69 | addTableAddress = false, 70 | extraExcludeKeys = { 71 | class = true, 72 | sprotos = true, 73 | }, 74 | } 75 | local snapshot = tabSerialization.createSnapshot(rootContext, detailControl) 76 | snapshot.errorMsgs = string.split(errorStack, "\n") 77 | 78 | local t1 = socket.gettime() 79 | print("tabSnapshotLogger create snapshot using time: ", t1 - t0) 80 | 81 | local fileSize = tabSerialization.saveSnapshotToFile(snapshot, filename) 82 | local t2 = socket.gettime() 83 | print("tabSnapshotLogger save snapshot to file using time: ", t2 - t1, "/", t2 - t0) 84 | print("tabSnapshotLogger fileSize ", fileSize / (1024*1024), "M") 85 | 86 | end 87 | -------------------------------------------------------------------------------- /tabSimpleClient.lua: -------------------------------------------------------------------------------- 1 | -- author cs 2 | -- email 04nycs@gmail.com 3 | -- https://github.com/ThinEureka/tabMachine 4 | -- created on Oct 18 2022 5 | local tabSocket = require("tabMachine.tabSocket") 6 | local tabSimpleClient = nil 7 | 8 | tabSimpleClient = _{ 9 | tabName = "tabSimpleClient", 10 | 11 | s1 = function(c, params) 12 | c._nickName = "client" 13 | c._tabSocket = c:call(tabSocket, "socket", nil, 15) 14 | c._tabCmdHandler = c:call(params.cmdHandler(params.debugCmd), "cmdHandler") 15 | end, 16 | 17 | s2 = function(c) 18 | c:suspend("s3") 19 | end, 20 | 21 | s3 = function(c, ip, port, timeout, tabErrHandler) 22 | c:call(c._tabSocket:connect(ip, port, timeout), "s4", {"isSuccess", "err"}) 23 | end, 24 | 25 | s5 = function(c) 26 | if c.isSuccess then 27 | c:call(tabSimpleClient.tabWorking(), "s6") 28 | else 29 | c:start("s2") 30 | end 31 | end, 32 | 33 | s7 = function(c) 34 | c._tabSocket:disconnect() 35 | c:start("s2") 36 | end, 37 | 38 | -- event & inner 39 | event = g_t.empty_event, 40 | inner = { 41 | tabSocket = function(c) 42 | return c._tabSocket 43 | end, 44 | 45 | tabCmdHandler = function(c) 46 | return c._tabCmdHandler 47 | end, 48 | 49 | service = function(c) 50 | return c 51 | end, 52 | }, 53 | 54 | -- public: 55 | connect = function(c, ip, port, timeout, tabErrHandler) 56 | c:resume("s3", ip, port, timeout, tabErrHandler) 57 | return c:tabProxy("s4", true) 58 | end, 59 | 60 | getTabSocket = function(c) 61 | return c._tabSocket 62 | end, 63 | 64 | tabInWorking = function(c, stopHostWhenStop) 65 | local sub = c:getSub("s6") 66 | if sub == nil then 67 | return nil 68 | else 69 | return sub:tabProxy(nil, stopHostWhenStop) 70 | end 71 | end, 72 | 73 | sendRpc = function(c, ...) 74 | return c._tabCmdHandler:sendRpc(...) 75 | end, 76 | 77 | sendRequest = function(c, ...) 78 | return c._tabCmdHandler:sendRequest(...) 79 | end, 80 | } 81 | 82 | tabSimpleClient.tabWorking = _{ 83 | s1 = function(c) 84 | c:call(c:_("tabSocket"):tabInState(tabSocket.STATE.CONNECTED), "s2") 85 | local tabCmdHandler = c:_("tabCmdHandler") 86 | c:call(c:_("tabSocket"):tabPullSegments(tabCmdHandler.decodeSegment, function(msg) 87 | tabCmdHandler:onNewSegment(msg) 88 | end), "pullMsg") 89 | end, 90 | 91 | s3 = function(c) 92 | c:stop() 93 | end, 94 | 95 | event = g_t.empty_event, 96 | } 97 | 98 | return tabSimpleClient 99 | 100 | -------------------------------------------------------------------------------- /tabDebugger.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --created on Jan 8, 2020 5 | 6 | local tabDebugger = class("tabDebugger") 7 | 8 | function tabDebugger:ctor(traceback) 9 | self._traceback = traceback 10 | end 11 | 12 | function tabDebugger:onMachineStart(machine, scName) 13 | local msg = g_frameIndex .. " tab start machine" 14 | if self._traceback then 15 | msg = msg .. "\n" .. debug.traceback() 16 | end 17 | print(msg) 18 | end 19 | 20 | function tabDebugger:onContextStart(context, scName) 21 | local msg = g_frameIndex .. " tab start " .. context:getDetailedPath(context) .. "." .. scName 22 | if self._traceback then 23 | msg = msg .. "\n" .. debug.traceback() 24 | end 25 | print(msg) 26 | end 27 | 28 | function tabDebugger:onContextQuit(context) 29 | local msg = g_frameIndex .. " tab quit " .. context:getDetailedPath(context) 30 | if self._traceback then 31 | msg = msg .. "\n" .. debug.traceback() 32 | end 33 | print(msg) 34 | end 35 | 36 | function tabDebugger:onContextStop(context) 37 | local msg = g_frameIndex .. " tab stop " .. context:getDetailedPath(context) 38 | if self._traceback then 39 | msg = msg .. "\n" .. debug.traceback() 40 | end 41 | print(msg) 42 | end 43 | 44 | function tabDebugger:onContextException(context, exception) 45 | local msg = g_frameIndex .. " tab throw exception " .. context:getDetailedPath(context) 46 | if self._traceback then 47 | msg = msg .. "\n" .. debug.traceback() 48 | end 49 | print(msg) 50 | end 51 | 52 | function tabDebugger:onTabCall(context, scName, tabName) 53 | local msg = g_frameIndex .. " tab call " .. context:getDetailedPath(context) .. "." .. scName 54 | if self._traceback then 55 | msg = msg .. "\n" .. debug.traceback() 56 | end 57 | print(msg) 58 | end 59 | 60 | function tabDebugger:onTabJoin(context, scName, scNames) 61 | local joins = table.concat(scNames, "") 62 | local msg = g_frameIndex .. " tab join " .. context:getDetailedPath(context) .. "." .. scName .. " " .. joins 63 | if self._traceback then 64 | msg = msg .. "\n" .. debug.traceback() 65 | end 66 | print(msg) 67 | end 68 | 69 | function tabDebugger:onTabSuspend(context, scName) 70 | local msg = g_frameIndex .. " tab suspend " .. context:getDetailedPath(context) .. "." .. scName 71 | if self._traceback then 72 | msg = msg .. "\n" .. debug.traceback() 73 | end 74 | print(msg) 75 | end 76 | 77 | function tabDebugger:onTabResume(context, scName) 78 | -- TODO 79 | local msg = g_frameIndex .. " tab resume " .. context:getDetailedPath(context) .. "." .. scName 80 | if self._traceback then 81 | msg = msg .. "\n" .. debug.traceback() 82 | end 83 | print(msg) 84 | end 85 | 86 | return tabDebugger 87 | -------------------------------------------------------------------------------- /tabProfiler.lua: -------------------------------------------------------------------------------- 1 | local tabProfiler = class("tabProfiler") 2 | local socket = require("socket") 3 | 4 | function tabProfiler:start() 5 | self.isRun = true 6 | self.recordList = {} 7 | self.curRecordStack = {} 8 | self.recordUpdateList = {} 9 | end 10 | 11 | function tabProfiler:stop() 12 | self.isRun = false 13 | 14 | print("----------tab start 消耗--------") 15 | table.sort(self.recordList, function (a, b) 16 | return a.costTime > b.costTime 17 | end) 18 | for k,v in ipairs(self.recordList) do 19 | print(v.key, "消耗时间为:", v.costTime * 1000, "ms") 20 | end 21 | 22 | local updateResult = {} 23 | for k,v in pairs(self.recordUpdateList) do 24 | table.insert(updateResult, v) 25 | end 26 | 27 | table.sort(updateResult, function(a, b) 28 | return a.maxTime > b.maxTime 29 | end) 30 | 31 | print("----------tab update 消耗--------") 32 | for k,v in ipairs(updateResult) do 33 | print(v.key..".update最大耗时时间为:", v.maxTime * 1000, "ms") 34 | end 35 | 36 | self.recordList = {} 37 | self.recordUpdateList = {} 38 | end 39 | 40 | function tabProfiler:beginSampleTime(key) 41 | if not self.isRun then 42 | return 43 | end 44 | local curTime = socket:gettime() 45 | local record = {key = key, startTime = curTime, costTime = 0} 46 | table.insert(self.recordList, record) 47 | local curIndex = #self.recordList 48 | table.insert(self.curRecordStack, curIndex) 49 | end 50 | 51 | function tabProfiler:endSampleTime() 52 | if not self.isRun then 53 | return 54 | end 55 | local curTime = socket:gettime() 56 | local curIndex = table.remove(self.curRecordStack) 57 | if not curIndex then 58 | return 59 | end 60 | local tempCostTime = 0 61 | for i = curIndex+1, #self.recordList do 62 | tempCostTime = tempCostTime + self.recordList[i].costTime 63 | end 64 | self.recordList[curIndex].costTime = curTime - self.recordList[curIndex].startTime - tempCostTime 65 | end 66 | 67 | function tabProfiler:beginSampleUpdateTime(key) 68 | if not self.isRun then 69 | return 70 | end 71 | self.startIndex = #self.recordList 72 | local curTime = socket:gettime() 73 | if not self.recordUpdateList[key] then 74 | self.recordUpdateList[key] = {key = key, startTime = curTime, maxTime = -1} 75 | else 76 | self.recordUpdateList[key].startTime = curTime 77 | end 78 | end 79 | 80 | function tabProfiler:endSampleUpdateTime(key) 81 | if not self.isRun then 82 | return 83 | end 84 | local curTime = socket:gettime() 85 | local data = self.recordUpdateList[key] 86 | if data then 87 | local costStartTime = 0 88 | for i = self.startIndex +1, #self.recordList do 89 | costStartTime = costStartTime + self.recordList[i].costTime 90 | end 91 | local costTime = curTime - data.startTime - costStartTime 92 | if data.maxTime < costTime then 93 | data.maxTime = costTime 94 | end 95 | end 96 | end 97 | 98 | return tabProfiler -------------------------------------------------------------------------------- /tabSimpleServer.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --created on Oct 18 2022 5 | 6 | local socket = require "socket" 7 | 8 | local tabMachine = require("tabMachine.tabMachine") 9 | local tabSimpleServer = nil 10 | 11 | tabSimpleServer = _{ 12 | s1 = function(c, isUsingIpV6) 13 | c._isUsingIpV6 = isUsingIpV6 14 | end, 15 | 16 | s2 = function(c) 17 | c:suspend("s3") 18 | end, 19 | 20 | s3 = function(c, tabService, serviceParams, port, address, timeout) 21 | c._server = c._isUsingIpV6 and socket.tcp6() or socket.tcp() 22 | c._port = port 23 | c._address = address 24 | c._timeout = timeout 25 | 26 | c._server:bind(c._address, c._port) 27 | c._server:listen() 28 | c._server:settimeout(c._timeout) 29 | c._serviceGroup = c:call(tabSimpleServer.tabServiceGroup(tabService, serviceParams), "serviceGroup") 30 | end, 31 | 32 | s4 = function(c) 33 | c.socket = nil 34 | c:call(tabSimpleServer.tabAccept(c._server) >> "socket", "s5") 35 | end, 36 | 37 | s6 = function(c) 38 | c._serviceGroup:acceptSocket(c.socket) 39 | c:start("s4") 40 | end, 41 | 42 | 43 | final = function(c) 44 | c._server:close() 45 | end, 46 | 47 | event = g_t.empty_event, 48 | 49 | inner = { 50 | service = function(c, ip, port) 51 | local targetService = nil 52 | c:forEachService(function(service) 53 | if service:getIp() == ip and service:getPort() == port then 54 | targetService = service 55 | return true 56 | end 57 | end) 58 | return targetService 59 | end, 60 | 61 | serviceCount = function(c) 62 | return c._serviceGroup:getServiceCount() 63 | end, 64 | }, 65 | 66 | --public: 67 | startService = function(c, tabService, serviceParams, port, address, timeout) 68 | address = address or "*" 69 | timeout = timeout or 0.0001 70 | c:resume("s3", tabService, serviceParams, port, address, timeout) 71 | end, 72 | 73 | forEachService = function(c, callback) 74 | return c._serviceGroup:forEachService(callback) 75 | end, 76 | } 77 | 78 | tabSimpleServer.tabServiceGroup = _{ 79 | s1 = function(c, tabService, serviceParams) 80 | c.tabService = tabService 81 | c.serviceParams = serviceParams 82 | c._nextServiceId = 1 83 | end, 84 | 85 | event = { 86 | [tabMachine.event_context_stop] = function(c, p, name, target) 87 | if name == "service" then 88 | c:upwardNotify("tabSimpleServer.serviceStopped", target) 89 | end 90 | end 91 | }, 92 | 93 | --internal: 94 | acceptSocket = function(c, socket) 95 | local ip, port = socket:getsockname() 96 | local service = c:_("service", ip, port) 97 | if service ~= nil then 98 | if service:reconnect(socket) then 99 | return 100 | else 101 | service:stop() 102 | service = nil 103 | end 104 | end 105 | 106 | service = c:call(c.tabService, "service", nil, socket, c._nextServiceId, c.serviceParams) 107 | c._nextServiceId = c._nextServiceId + 1 108 | c:registerLifeTimeListener("service", c) 109 | c:upwardNotify("tabSimpleServer.serviceStarted", service) 110 | end, 111 | 112 | --public: 113 | getServiceCount = function(c) 114 | local count = 0 115 | c:forEachSub(function() 116 | count = count + 1 117 | end) 118 | return count 119 | end, 120 | 121 | forEachService = function(c, callback) 122 | c:forEachSub(function(sub) 123 | if sub.__name == "service" then 124 | return callback(sub) 125 | else 126 | return false 127 | end 128 | end) 129 | end, 130 | 131 | } 132 | 133 | tabSimpleServer.tabAccept = _{ 134 | tabName = "tabSimpleServer:tabAccept", 135 | 136 | s1 = function(c, socket) 137 | c.socket = socket 138 | end, 139 | 140 | s1_update = function(c) 141 | local client = c.socket:accept() 142 | if client ~= nil then 143 | c:output(client) 144 | c:stop() 145 | end 146 | end, 147 | } 148 | 149 | return tabSimpleServer 150 | 151 | -------------------------------------------------------------------------------- /cocosTabMachine.lua: -------------------------------------------------------------------------------- 1 | 2 | --author cs 3 | --email 04nycs@gmail.com 4 | --https://github.com/ThinEureka/tabMachine 5 | --created on July 13, 2019 6 | 7 | local tabMachine = require("tabMachine.tabMachine") 8 | 9 | local cocosTabMachine = tabMachine 10 | 11 | local cocosContext = require("tabMachine.cocosContext") 12 | 13 | -------------------------- cocosTabMachine ---------------------- 14 | 15 | cocosTabMachine.p_ctor = tabMachine.ctor 16 | cocosTabMachine.p_createException = tabMachine._createException 17 | 18 | function cocosTabMachine:ctor() 19 | cocosTabMachine.p_ctor(self) 20 | self.__scheduler = self:createSystemScheduler() 21 | end 22 | 23 | function cocosTabMachine:createSystemScheduler() 24 | return self:createScheduler(true) 25 | end 26 | 27 | local scheduler = class("scheduler") 28 | cocosTabMachine.schedulerClass = scheduler 29 | 30 | function scheduler:ctor(isSystem) 31 | self._isSystem = isSystem 32 | self._timeScale = 1 33 | self._timerMgrList = {} 34 | end 35 | 36 | function scheduler:createTimer(target, callback, interval, timerMgrType) 37 | if timerMgrType == nil then 38 | timerMgrType = 1 --g_t.updateTimerMgr_normal 39 | end 40 | 41 | local timerMgr = self._timerMgrList[timerMgrType] 42 | if not timerMgr then 43 | timerMgr = require("framework.updater.timerMgr").new() 44 | timerMgr:setTimeScale(self._timeScale) 45 | self._timerMgrList[timerMgrType] = timerMgr 46 | updateFunctionAddTimerMgr(timerMgr, timerMgrType) 47 | end 48 | return timerMgr:createTimer(target, callback, interval, false) 49 | end 50 | 51 | function scheduler:destroyTimer(handler, timerMgrType) 52 | if timerMgrType == nil then 53 | timerMgrType = 1 --g_t.updateTimerMgr_normal 54 | end 55 | local timerMgrList = self._timerMgrList 56 | if timerMgrList == nil then 57 | return 58 | end 59 | local timerMgr = timerMgrList[timerMgrType] 60 | timerMgr:removeTimer(handler) 61 | end 62 | 63 | function scheduler:pause() 64 | if self._isSystem then 65 | return 66 | end 67 | local timerMgrList = self._timerMgrList 68 | if timerMgrList == nil then 69 | return 70 | end 71 | for k,v in pairs(timerMgrList) do 72 | v:pause() 73 | end 74 | end 75 | 76 | function scheduler:resume() 77 | if self._isSystem then 78 | return 79 | end 80 | local timerMgrList = self._timerMgrList 81 | if timerMgrList == nil then 82 | return 83 | end 84 | for k,v in pairs(timerMgrList) do 85 | v:resume() 86 | end 87 | end 88 | 89 | function scheduler:setTimeScale(timeScale) 90 | if self._isSystem then 91 | return 92 | end 93 | self._timeScale = timeScale 94 | local timerMgrList = self._timerMgrList 95 | if timerMgrList == nil then 96 | return 97 | end 98 | 99 | for k,v in pairs(timerMgrList) do 100 | v:setTimeScale(timeScale) 101 | end 102 | end 103 | 104 | function scheduler:getTimeScale() 105 | return self._timeScale 106 | end 107 | 108 | function scheduler:isPaused() 109 | return false 110 | end 111 | 112 | function scheduler:dispose() 113 | if self._isSystem then 114 | return 115 | end 116 | 117 | local timerMgrList = self._timerMgrList 118 | if timerMgrList == nil then 119 | return 120 | end 121 | for k,v in pairs(timerMgrList) do 122 | updateFunctionRemoveTimerMgr(v, k) 123 | end 124 | self._timerMgrList = nil 125 | end 126 | 127 | function cocosTabMachine:createScheduler(isSystem) 128 | return scheduler.new(isSystem) 129 | end 130 | 131 | function cocosTabMachine:getScheduler() 132 | return self.__scheduler 133 | end 134 | 135 | function cocosTabMachine:getObject(path) 136 | if self.__rootContext == nil then 137 | return nil 138 | end 139 | 140 | return self.__rootContext:getObject(path) 141 | end 142 | 143 | --inline optimization 144 | -- function cocosTabMachine:_recycleContext(context) 145 | -- table.insert(__contextPool, context) 146 | -- end 147 | 148 | -- function cocosTabMachine:_addContextException(e, context) 149 | -- if e.errorTabStatcks == nil then 150 | -- e.errorTabStatcks = {} 151 | -- end 152 | -- 153 | -- table.insert(e.errorTabStatcks, context:getDetailedPath()) 154 | -- end 155 | 156 | -- function cocosTabMachine:_onUnCaughtException(e) 157 | -- dump(e, "uncaught exception", 100, printError) 158 | -- 159 | -- 上报 160 | -- local eMsg = "" 161 | -- local errorMsg = e.errorMsg or "no errorMsg" 162 | -- local reportVals = self:getObject("report") and self:getObject("report"):getTreeMsg() or "no reportVals" 163 | -- local errorTabStatcks = e.errorTabStatcks and self:prettyStr(e.errorTabStatcks or {}) or "no errorTabStatcks" 164 | -- local luaStackTrace = e.luaStackTrace or "no luaStackTrace" 165 | -- 166 | -- local strTop = "==== errorMsg ====\n" 167 | -- eMsg = eMsg .. strTop .. errorMsg 168 | -- strTop = "\n\n==== reportVals ====\n" 169 | -- eMsg = eMsg .. strTop .. reportVals 170 | -- strTop = "\n\n==== errorTabStatcks ====\n" 171 | -- eMsg = eMsg .. strTop .. errorTabStatcks 172 | -- strTop = "\n\n==== luaStackTrace ====\n" 173 | -- eMsg = eMsg .. strTop .. luaStackTrace 174 | -- if fabric then 175 | -- fabric:getInstance():allSet(tostring(errorMsg), eMsg, errorTabStatcks) 176 | -- end 177 | -- 178 | -- if g_enableDumpTabSnapshotOnCaughtException then 179 | -- if tabSnapshotLogger then 180 | -- tabSnapshotLogger:getInstance():dumpTabSnapshot(tostring(errorMsg), eMsg, errorTabStatcks) 181 | -- end 182 | -- end 183 | -- end 184 | -- 185 | -- function cocosTabMachine:_disposeContext(context) 186 | -- if context.dispose then 187 | -- context:dispose() 188 | -- end 189 | -- end 190 | 191 | -- function cocosTabMachine:prettyStr(arr) 192 | -- local str = "" 193 | -- for _,v in ipairs(arr or {}) do 194 | -- str = str .. v .. "\n" 195 | -- end 196 | -- return str 197 | -- end 198 | 199 | return cocosTabMachine 200 | 201 | -------------------------------------------------------------------------------- /tabDebuggerTrace.lua: -------------------------------------------------------------------------------- 1 | local cjson = require("cjson") 2 | local kTid = 1024 3 | local kPid = 1024 4 | local kCat = "tabMachine" 5 | local Frequency = CS.System.Diagnostics.Stopwatch.Frequency 6 | local microsecondPerTick = (1000 * 1000) / Frequency 7 | 8 | 9 | local tabDebuggerTrace = class("tabDebuggerTrace") 10 | 11 | function tabDebuggerTrace:ctor() 12 | self._ContextStartAsDurationEvents = false 13 | self._traceEvents = {} 14 | self._stopwatch = CS.System.Diagnostics.Stopwatch.StartNew() 15 | end 16 | 17 | function tabDebuggerTrace:close() 18 | local success, filePath, fileSize = self:saveTraceFile() 19 | print(filePath, success, fileSize) 20 | end 21 | 22 | function tabDebuggerTrace:getTimestamp() 23 | local ticks = self._stopwatch.ElapsedTicks 24 | return microsecondPerTick * ticks 25 | end 26 | 27 | function tabDebuggerTrace:getDetailedPath(context) 28 | local c = context 29 | local name = nil 30 | if c then 31 | name = c.__name 32 | else 33 | name = "" 34 | end 35 | 36 | return tostring(name) 37 | end 38 | 39 | local function on_error(e) 40 | printError(e) 41 | end 42 | function tabDebuggerTrace:saveTraceFile() 43 | local context = "" 44 | local success = false 45 | local fileSize = 0 46 | local fileName = os.date('trace_%Y-%m-%d-%H-%M-%S') 47 | local filePath = CS.System.IO.Path.Combine(CS.UnityEngine.Application.persistentDataPath, "tab_trace/" .. fileName .. ".json") 48 | success = xpcall(function() 49 | CS.SystemIOUtils.EnsureFileDirectoryExists(filePath) 50 | local data = { 51 | traceEvents = self._traceEvents, 52 | } 53 | context = cjson.encode(data) 54 | 55 | local file, err = io.open(filePath, "wb") 56 | if not err and file then 57 | file:write(context) 58 | fileSize = file:seek() 59 | file:close() 60 | end 61 | end, on_error) 62 | return success, filePath, fileSize 63 | end 64 | 65 | function tabDebuggerTrace:onMachineStart(machine, scName) 66 | local msg = g_frameIndex .. " tab start machine" 67 | if self._traceback then 68 | msg = msg .. "\n" .. debug.traceback() 69 | end 70 | print(msg) 71 | end 72 | 73 | function tabDebuggerTrace:onContextStart(context, scName) 74 | local fullName = context:getDetailedPath(context) .. "." .. scName 75 | if self._ContextStartAsDurationEvents then 76 | -- As Duration Events 77 | local name = scName 78 | local ts = self:getTimestamp() 79 | local args = {fullName = fullName} 80 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "B", name = name, args = args } 81 | table.insert(self._traceEvents, traceEvent) 82 | local ts = self:getTimestamp() 83 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "E", name = name, args = args } 84 | table.insert(self._traceEvents, traceEvent) 85 | else 86 | -- As Instant Events 87 | local name = "[tab start]" .. scName 88 | local ts = self:getTimestamp() 89 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name} 90 | table.insert(self._traceEvents, traceEvent) 91 | end 92 | end 93 | 94 | function tabDebuggerTrace:onTabCall(context, scName, tabName) 95 | local name = scName 96 | local fullName = context:getDetailedPath(context) .. "." .. scName 97 | local args = {fullName = fullName} 98 | if type(tabName) == "string" and tabName ~= "context" then 99 | args.tabName = tabName 100 | end 101 | local ts = self:getTimestamp() 102 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "B", name = name, args = args } 103 | table.insert(self._traceEvents, traceEvent) 104 | end 105 | 106 | function tabDebuggerTrace:onContextStop(context) 107 | local name = self:getDetailedPath(context) 108 | local fullName = context:getDetailedPath(context) 109 | local args = {fullName = fullName} 110 | if type(context.tabName) == "string" and context.tabName ~= "context" then 111 | args.tabName = context.tabName 112 | end 113 | local ts = self:getTimestamp() 114 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "E", name = name, args = args } 115 | table.insert(self._traceEvents, traceEvent) 116 | end 117 | 118 | function tabDebuggerTrace:onContextQuit(context) 119 | local name = "[tab quit]" ..self:getDetailedPath(context) 120 | local fullName = context:getDetailedPath(context) 121 | local ts = self:getTimestamp() 122 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name } 123 | table.insert(self._traceEvents, traceEvent) 124 | end 125 | 126 | function tabDebuggerTrace:onContextException(context, exception) 127 | local name = "[tab exception]" ..self:getDetailedPath(context) 128 | local fullName = context:getDetailedPath(context) 129 | local ts = self:getTimestamp() 130 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name } 131 | table.insert(self._traceEvents, traceEvent) 132 | end 133 | 134 | function tabDebuggerTrace:onTabJoin(context, scName, scNames) 135 | local joins = table.concat(scNames, "") 136 | local name = "[tab join]" .. scName 137 | local fullName = context:getDetailedPath(context) .. "." .. scName .. " " .. joins 138 | local ts = self:getTimestamp() 139 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name} 140 | table.insert(self._traceEvents, traceEvent) 141 | end 142 | 143 | function tabDebuggerTrace:onTabSuspend(context, scName) 144 | local name = "[tab suspend]" .. scName 145 | local fullName = context:getDetailedPath(context) .. "." .. scName 146 | local ts = self:getTimestamp() 147 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name} 148 | table.insert(self._traceEvents, traceEvent) 149 | end 150 | 151 | function tabDebuggerTrace:onTabResume(context, scName) 152 | local name = "[tab resume]" .. scName 153 | local name = "[tab resume]" .. scName 154 | local fullName = context:getDetailedPath(context) .. "." .. scName 155 | local ts = self:getTimestamp() 156 | local traceEvent = { cat = kCat, pid = kPid, tid = kTid, ts = ts, ph = "i", name = name} 157 | table.insert(self._traceEvents, traceEvent) 158 | end 159 | 160 | return tabDebuggerTrace 161 | -------------------------------------------------------------------------------- /tabHttp.lua: -------------------------------------------------------------------------------- 1 | 2 | g_t.httpGetJson = _{ 3 | s1 = function(c, uri, timeout) 4 | c.uri = uri 5 | c.requestId = CS.Utils.HttpRequestGet(uri, timeout or 10, function(status, downloadText) 6 | c:_onRespond(status, downloadText) 7 | end) 8 | c.json = require("cjson") 9 | end, 10 | 11 | _onRespond = function(c, status, downloadText) 12 | c.requestEnd = true 13 | if status == 1 then 14 | local isOk, ret = pcall(function() return c.json.decode(downloadText) end) 15 | if isOk then 16 | c:output(true, ret) 17 | else 18 | c:output(false) 19 | end 20 | else 21 | c:output(false) 22 | end 23 | c:stop() 24 | end, 25 | 26 | final = function(c) 27 | if not c.requestEnd then 28 | CS.Utils.StopRequest(c.requestId) 29 | end 30 | end, 31 | 32 | s1_event = g_t.empty_event, 33 | 34 | __addNickName = function(c) 35 | c._nickName = "httpGetJson<" .. (c.uri) .. ">" 36 | end, 37 | } 38 | 39 | g_t.httpGetJsonWithCompete = _{ 40 | s1 = function(c, urls, timeout) 41 | local tabs = {} 42 | for _, url in ipairs(urls) do 43 | local tab = g_t.httpGetJson(url, timeout) 44 | table.insert(tabs, tab) 45 | end 46 | c:call(g_t.compete(tabs), "s2", {"index", "json"}) 47 | end, 48 | 49 | s3 = function(c) 50 | c:output(c.index~=nil, c.json) 51 | end, 52 | 53 | __addNickName = function(c) 54 | c._nickName = "httpGetJsonWithCompete" 55 | end, 56 | } 57 | 58 | g_t.httpGetData = _{ 59 | s1 = function(c, uri, timeout) 60 | c.requestId = CS.Utils.HttpRequestGet(uri, timeout or 10, function(status, downloadText, data) 61 | c:_onRespond(status, downloadText, data) 62 | end) 63 | end, 64 | 65 | _onRespond = function(c, status, downloadText, data) 66 | c.requestEnd = true 67 | if status == 1 then 68 | c:output(true, data) 69 | else 70 | c:output(false) 71 | end 72 | c:stop() 73 | end, 74 | 75 | final = function(c) 76 | if not c.requestEnd then 77 | CS.Utils.StopRequest(c.requestId) 78 | end 79 | end, 80 | 81 | s1_event = g_t.empty_event, 82 | 83 | __addNickName = function(c) 84 | c._nickName = "httpGetData" 85 | end, 86 | } 87 | 88 | 89 | g_t.httpPost = _{ 90 | s1 = function(c, uri, postData, contentType, timeout) 91 | c.uri = uri 92 | c.requestId = CS.Utils.HttpRequestPost(uri, postData, contentType or "application/json", timeout or 10,function(status, downloadText) 93 | c:_onRespond(status, downloadText) 94 | end) 95 | end, 96 | 97 | _onRespond = function(c, status, downloadText) 98 | c.requestEnd = true 99 | if status == 1 then 100 | c:output(true, downloadText) 101 | else 102 | c:output(false) 103 | end 104 | c:stop() 105 | end, 106 | 107 | final = function(c) 108 | if not c.requestEnd then 109 | CS.Utils.StopRequest(c.requestId) 110 | end 111 | end, 112 | 113 | s1_event = g_t.empty_event, 114 | } 115 | 116 | g_t.httpPostFormData = _{ 117 | s1 = function(c, uri, fromData, timeout) 118 | c.uri = uri 119 | c.requestId = CS.Utils.HttpRequestPostFormData(uri, fromData, timeout or 10, function(status, downloadText) 120 | c:_onRespond(status, downloadText) 121 | end) 122 | end, 123 | 124 | _onRespond = function(c, status, downloadText) 125 | c.requestEnd = true 126 | if status == 1 then 127 | c:output(true, downloadText) 128 | else 129 | c:output(false) 130 | end 131 | c:stop() 132 | end, 133 | 134 | final = function(c) 135 | if not c.requestEnd then 136 | CS.Utils.StopRequest(c.requestId) 137 | end 138 | end, 139 | 140 | s1_event = g_t.empty_event, 141 | } 142 | 143 | 144 | g_t.httpError = { 145 | webRequestError = 1, 146 | fileSizeNotMatch = 2, 147 | md5NotMatch = 3, 148 | } 149 | 150 | -- if fileSize is not nil, check the tmp file size first then continue from where last time aborted 151 | -- if you do not need this behavior, delete the tmp file by yourself. 152 | g_t.httpSafeDownload = _{ 153 | s1 = function(c, url, savePath, enableProgressEvent, fileSize, md5, tmpPath, progressInterval) 154 | c.startTime = require("socket").gettime() 155 | local downloadedSize = 0 156 | c.savePath = savePath 157 | tmpPath = tmpPath or (c.savePath .. ".tmp") 158 | c.tmpPath = tmpPath 159 | c.md5 = md5 160 | c.fileSize = fileSize 161 | c.url = url 162 | if g_t.debug then 163 | c:__addNickName() 164 | end 165 | c.report = {url = url, savePath = savePath, existSize = downloadedSize, totalSize = fileSize, md5 = md5} 166 | 167 | if c:fileExists(tmpPath) then 168 | if fileSize ~= nil then 169 | local tmpFileSize = c:getFileSize(tmpPath) 170 | if tmpFileSize == fileSize then 171 | c.ret = {result = true} 172 | c:start("s3") 173 | return 174 | elseif tmpFileSize > fileSize then 175 | c:deleteFile(tmpPath) 176 | else 177 | downloadedSize = tmpFileSize 178 | end 179 | else 180 | c:deleteFile(tmpPath) 181 | end 182 | end 183 | 184 | local csTab = CS.TabHttp.TabDownloadFile(url, tmpPath, function(downloaded) 185 | if enableProgressEvent then 186 | c:upwardNotify("downloadProgress", downloaded) 187 | end 188 | end, downloadedSize, progressInterval or 500) 189 | c.report.existSize = downloadedSize 190 | c:call(g_t.tabCS(csTab) >> "ret", "s2") 191 | end, 192 | 193 | s3 = function(c) 194 | c.report.result = c.ret and c.ret.result 195 | c.report.time = require("socket").gettime() - c.startTime 196 | 197 | if c.ret.result == true then 198 | if c.fileSize ~= nil then 199 | local downloadedSize = c:getFileSize(c.tmpPath) 200 | if downloadedSize ~= c.fileSize then 201 | c:deleteFile(c.tmpPath) 202 | c.report.error = g_t.httpError.fileSizeNotMatch 203 | c:output(false, g_t.httpError.fileSizeNotMatch, c.report) 204 | return 205 | end 206 | end 207 | 208 | if c.md5 ~= nil then 209 | local md5 = c:getFileMd5(c.tmpPath) 210 | if md5 ~= c.md5 then 211 | c:deleteFile(c.tmpPath) 212 | c.report.error = g_t.httpError.md5NotMatch 213 | c:output(false, g_t.httpError.md5NotMatch, c.report) 214 | return 215 | end 216 | end 217 | 218 | if c:fileExists(c.savePath) then 219 | c:deleteFile(c.savePath) 220 | end 221 | 222 | c:moveFile(c.tmpPath, c.savePath) 223 | local downloadedSize = c:getFileSize(c.savePath) 224 | c:upwardNotify("downloadProgress", downloadedSize) 225 | c.report.fileSize = downloadedSize 226 | c:output(true, nil, c.report) 227 | else 228 | c.report.error = g_t.httpError.webRequestError 229 | c:output(false, g_t.httpError.webRequestError, c.report) 230 | return 231 | end 232 | end, 233 | 234 | __addNickName = function(c) 235 | c._nickName = "safeDownload:" .. c.url 236 | end, 237 | 238 | --private: 239 | fileExists = function(c, path) 240 | return CS.Framework.FileUtils.IsFileExists(path) 241 | end, 242 | 243 | deleteFile = function(c, path) 244 | CS.System.IO.File.Delete(path) 245 | end, 246 | 247 | getFileSize = function(c, path) 248 | return CS.Utils.GetFileSize(path) 249 | end, 250 | 251 | getFileMd5 = function(c, path) 252 | return CS.Framework.MD5Utils.GetFileMD5(path) 253 | end, 254 | 255 | moveFile = function(c, oldPath, newPath) 256 | CS.System.IO.File.Move(oldPath, newPath) 257 | end, 258 | } 259 | 260 | g_t.tabGetFileSize = _{ 261 | s1 = function(c, url) 262 | local csTab = CS.TabHttp.TabGetFileSize(url) 263 | c:call(g_t.tabCS(csTab) >> "result", "s2") 264 | end, 265 | 266 | s3 = function(c) 267 | if c.result.isSuccess then 268 | c:output(c.result.size) 269 | else 270 | c:output(-1, c.result) 271 | end 272 | end, 273 | } 274 | -------------------------------------------------------------------------------- /tabHotReload.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --04nycs@gmail.com 3 | -- 4 | --https://github.com/ThinEureka/tabMachine 5 | --created on Oct 21, 2023 6 | -- 7 | 8 | local tabHotReload = {} 9 | 10 | tabHotReload.enableLog = false 11 | 12 | tabHotReload.logs = { 13 | -- reload_packages = {}, 14 | reload_tabs = {}, 15 | non_reload_tabs = {} 16 | } 17 | 18 | tabHotReload.baseExcludes = { 19 | "_G", 20 | "package", 21 | "string", 22 | "coroutine", 23 | "os", 24 | "io", 25 | "debug", 26 | "math", 27 | "table", 28 | "utf8", 29 | 30 | "socket", 31 | "crypt", 32 | "lpeg", 33 | 34 | "framework.*", 35 | "tabMachine.*", 36 | 37 | "luaconfig", 38 | } 39 | 40 | function tabHotReload.hotReload(rootContext, extraExcludes, includes) 41 | local packages = tabHotReload.getReloadPackages(extraExcludes, includes) 42 | 43 | if tabHotReload.enableLog then 44 | tabHotReload.logs.reload_packages = packages 45 | end 46 | 47 | local tabMap, rTabMap = tabHotReload.buildTabMap(packages) 48 | local contextMap = tabHotReload.buildContextMap(rTabMap, rootContext) 49 | tabHotReload.reloadPackage(packages) 50 | tabMap, rTabMap = tabHotReload.buildTabMap(packages) 51 | tabHotReload.reloadTabForContexts(contextMap, tabMap) 52 | end 53 | 54 | function tabHotReload.isPackageInList(packPath, list) 55 | for _, path in ipairs(list) do 56 | local plain = path:find("*", 1, true) == nil 57 | if plain then 58 | if packPath == path then 59 | return true 60 | end 61 | else 62 | local index, endIndex = packPath:find(path, 1) 63 | if index == 1 and endIndex == #packPath then 64 | return true 65 | end 66 | end 67 | end 68 | end 69 | 70 | function tabHotReload.getReloadPackages(extraExcludes, includes) 71 | local baseExcludes = tabHotReload.baseExcludes 72 | local packages = {} 73 | for packPath, pack in pairs(package.loaded) do 74 | if type(pack) == "table" then 75 | local isIncluded = true 76 | if includes ~= nil then 77 | isIncluded = tabHotReload.isPackageInList(packPath, includes) 78 | end 79 | 80 | if isIncluded then 81 | isIncluded = not tabHotReload.isPackageInList(packPath, baseExcludes) 82 | end 83 | 84 | if isIncluded and extraExcludes ~= nil then 85 | isIncluded = not tabHotReload.isPackageInList(packPath, extraExcludes) 86 | end 87 | 88 | if isIncluded then 89 | packages[packPath] = pack 90 | end 91 | end 92 | end 93 | 94 | return packages 95 | end 96 | 97 | function tabHotReload.buildTabMap(packages) 98 | local tabMap = {} 99 | local uniqueMap = {} 100 | local addTable = nil 101 | addTable = function(path, table) 102 | if uniqueMap[table] == nil then 103 | uniqueMap[table] = table 104 | 105 | if rawget(table, "__hooked") then 106 | tabMap[path] = table 107 | end 108 | 109 | for k, v in pairs(table) do 110 | local tk = type(k) 111 | local tv = type(v) 112 | if tv == "table" and (tk == "number" or tk == "string") and k ~= "super" and k~= "__index" then 113 | local newPath = path .. ".@" .. k 114 | addTable(newPath, v) 115 | end 116 | end 117 | end 118 | end 119 | 120 | for packPath, pack in pairs(packages) do 121 | addTable(packPath, pack) 122 | end 123 | 124 | local rTabMap = {} 125 | for path, tab in pairs(tabMap) do 126 | rTabMap[tab] = path 127 | end 128 | 129 | return tabMap, rTabMap 130 | end 131 | 132 | function tabHotReload.reloadPackage(packages) 133 | for path, _ in pairs(packages) do 134 | package.loaded[path] = nil 135 | end 136 | 137 | for path, _ in pairs(packages) do 138 | pcall(function() 139 | packages[path] = require(path) 140 | end) 141 | end 142 | end 143 | 144 | function tabHotReload.buildContextMap(rTabMap, rootContext) 145 | local contextMap = {} 146 | local function addContext(c) 147 | local tab = c.__tab 148 | if tab ~= nil then 149 | local tabPath = rTabMap[tab] 150 | if tabPath ~= nil then 151 | contextMap[c] = tabPath 152 | else 153 | if tabHotReload.enableLog then 154 | local log = { 155 | context_path = c:getDetailedPath(), 156 | reason = "dynamic tab can not be reloaded", 157 | } 158 | table.insert(tabHotReload.logs.non_reload_tabs, log) 159 | end 160 | end 161 | else 162 | if tabHotReload.enableLog then 163 | local log = { 164 | context_path = c:getDetailedPath(), 165 | reason = "no tab", 166 | } 167 | table.insert(tabHotReload.logs.non_reload_tabs, log) 168 | end 169 | end 170 | 171 | if c.__subContexts ~= nil then 172 | for _, subContext in ipairs(c.__subContexts) do 173 | addContext(subContext) 174 | end 175 | end 176 | end 177 | 178 | addContext(rootContext) 179 | return contextMap 180 | end 181 | 182 | function tabHotReload.reloadTabForContexts(contextMap, tabMap) 183 | for context, tabPath in pairs(contextMap) do 184 | local newTab = tabMap[tabPath] 185 | if newTab ~= nil then 186 | tabHotReload.reloadTabForContext(context, newTab) 187 | if tabHotReload.enableLog then 188 | local log = { 189 | context_path = context:getDetailedPath(), 190 | tab_path = tabPath 191 | } 192 | table.insert(tabHotReload.logs.reload_tabs, log) 193 | end 194 | else 195 | if tabHotReload.enableLog then 196 | local log = { 197 | context_path = context:getDetailedPath(), 198 | tab_path = tabPath, 199 | reason = "can not find tab path", 200 | } 201 | table.insert(tabHotReload.logs.non_reload_tabs, log) 202 | end 203 | end 204 | end 205 | end 206 | 207 | function tabHotReload.reloadTabForContext(context, newTab) 208 | local tm = g_tm 209 | tm.compileTab(newTab) 210 | 211 | local oldMetatable = getmetatable(context) 212 | if oldMetatable == context.__tab then 213 | setmetatable(context, newTab) 214 | end 215 | 216 | context.__tab = newTab 217 | 218 | context.__finalFun = newTab.final 219 | context.__event = newTab.event 220 | context.__catchFun = newTab.catch 221 | -- local oldUpdateFun = context.__updateFun 222 | context.__updateFun = newTab.update 223 | context.__updateInterval = newTab.updateInterval 224 | context.__updateTimerMgr = newTab.updateTimerMgr 225 | 226 | 227 | -- if oldUpdateFun ~= nil then 228 | -- tabHotReload.reloadUpdateFun(context, oldUpdateFun, context.__updateFun) 229 | -- end 230 | 231 | local selfTab = newTab 232 | if context.__subContexts ~= nil then 233 | for _, subContext in ipairs(context.__subContexts) do 234 | if subContext.__tab == nil then 235 | local subUpdateFunEx 236 | local subUpdateIntevalEx 237 | local subUpdateTimerMgrEx 238 | local eventEx 239 | local subFinalFunEx 240 | local subCatchFunEx 241 | 242 | local commonLabels = tm.__commonLabelCache[subContext.__name] 243 | if commonLabels then 244 | subUpdateFunEx = selfTab[commonLabels.update] 245 | local dynamics = context.__dynamics 246 | if dynamics ~= nil then 247 | local dynamicLabels = dynamics[scName] 248 | if dynamicLabels ~= nil then 249 | subUpdateIntevalEx = dynamicLabels.updateInterval 250 | end 251 | end 252 | 253 | if subUpdateIntevalEx == nil then 254 | subUpdateIntevalEx = selfTab[commonLabels.updateInterval] 255 | end 256 | subUpdateTimerMgrEx = selfTab[commonLabels.updateTimerMgr] 257 | eventEx = selfTab[commonLabels.event] 258 | subFinalFunEx = selfTab[commonLabels.final] 259 | subCatchFunEx = selfTab[commonLabels.catch] 260 | 261 | 262 | end 263 | 264 | -- local oldUpdate = subContext.__updateFunEx 265 | subContext.__updateFunEx = subUpdateFunEx 266 | subContext.__updateIntervalEx = subUpdateIntevalEx 267 | subContext.__updateTimerMgrEx = subUpdateTimerMgrEx 268 | subContext.__eventEx = eventEx 269 | subContext.__finalFunEx = subFinalFunEx 270 | subContext.__catchFunEx = subCatchFunEx 271 | 272 | -- if oldUpdate ~= nil then 273 | -- tabHotReload.reloadUpdateFun(subContext, oldUpdate, newUpdate) 274 | -- end 275 | end 276 | end 277 | end 278 | end 279 | 280 | --reload updateFun 281 | -- function tabHotReload.reloadUpdateFun(context, oldUpdate, newUpdate) 282 | -- local timerMgr = g_timerMgr 283 | -- 284 | -- for k, v in ipairs(timerMgr.timerList) do 285 | -- if v.target == context and v.callBack == oldUpdate then 286 | -- v.callBack = newUpdate 287 | -- end 288 | -- end 289 | -- end 290 | 291 | 292 | return tabHotReload 293 | -------------------------------------------------------------------------------- /tabLanes.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --multi-theading support with luaLanes(https://lualanes.github.io/lanes) 5 | --your project should integrate luaLanes to use following code 6 | --to use asyncRequireWithShareData, you also need to interate conf(https://github.com/cloudwu/lua-conf) 7 | --created on Aug 26, 2021 8 | 9 | -- With conf, you can share data between 2 lua states reducing the 10 | -- overhead of cloning data. 11 | g_t.asyncRequireWithShareData = function (modules) 12 | return _{ 13 | s1 = function(c) 14 | c._nickName = "asyncRequireWithShareData" .. #modules 15 | local lanes = require("lanes") 16 | local linda = lanes.linda() 17 | c.linda = linda 18 | local function load() 19 | local index = 1 20 | local sendNum = 0 21 | local conf = require("conf") 22 | while true do 23 | local name = modules[index] 24 | if name == nil then 25 | break 26 | end 27 | 28 | local m= require(name) 29 | local t = conf.host.new(m) 30 | linda:send(0, "m", t) 31 | index = index + 1 32 | end 33 | end 34 | 35 | c.a = lanes.gen( "package", "table", load)() 36 | c.index = 1 37 | end, 38 | 39 | s1_update = function(c) 40 | local key, t = c.linda:receive(0, "m") -- timeout in seconds 41 | if t == nil then 42 | return 43 | end 44 | 45 | local box = conf.box(t) 46 | package.loaded[modules[c.index]] = box 47 | 48 | 49 | c.index = c.index + 1 50 | if c.index >= #modules then 51 | c:stop() 52 | end 53 | end, 54 | } 55 | end 56 | 57 | -- Without conf, you need to disassemble big tables in the working thread 58 | -- and reasseble them in the main thread to avoid blocking the main thread. 59 | -- However, the total time can't be saved and you also need to pay for the 60 | -- overhead of this mechanism. 61 | local builderTable = class("builderTable") 62 | 63 | function builderTable:ctor() 64 | self._value = {} 65 | end 66 | 67 | function builderTable:append(key, value) 68 | self._value[key] = value 69 | end 70 | 71 | function builderTable:complete() 72 | return self._value 73 | end 74 | 75 | 76 | local function isEndCmd(k) 77 | return k == "end" 78 | end 79 | 80 | local function isCmd(k) 81 | return k == "table" 82 | end 83 | 84 | local builderMergeTable = class("builderMergeTable") 85 | 86 | function builderMergeTable:ctor() 87 | self._value = {} 88 | end 89 | 90 | function builderMergeTable:append(key, value) 91 | for k, v in pairs(value) do 92 | self._value[k] = v 93 | end 94 | end 95 | 96 | function builderMergeTable:complete() 97 | return self._value 98 | end 99 | 100 | g_t.tabReceiveStreamedTable = function (linda, frameTimeout) 101 | frameTimeout = frameTimeout or 0 102 | return _{ 103 | s1 = function(c) 104 | -- c:s1_update() 105 | c.receiveNum = 0 106 | end, 107 | 108 | s1_update = function(c) 109 | local t1 = socket.gettime() 110 | while true do 111 | local p1 = socket.gettime() 112 | local k, v = linda:receive(0, "m") 113 | local p2 = socket.gettime() 114 | 115 | if v == nil then 116 | return 117 | end 118 | 119 | c.receiveNum = (c.receiveNum + 1) % 10 120 | 121 | 122 | if c.lastBuilder ~= nil then 123 | if not v.isEnd then 124 | if v.cmd == nil then 125 | c.lastBuilder:append(v.key, v.value) 126 | else 127 | local builder = c:_createBuilder(v.cmd) 128 | builder.parentBuilder = c.lastBuilder 129 | builder.parentKey = v.key 130 | c.lastBuilder = builder 131 | end 132 | else 133 | local value = c.lastBuilder:complete() 134 | local key = c.lastBuilder.parentKey 135 | c.lastBuilder = c.lastBuilder.parentBuilder 136 | 137 | if c.lastBuilder ~= nil then 138 | c.lastBuilder:append(key, value) 139 | else 140 | c:output(value) 141 | c:stop() 142 | return 143 | end 144 | end 145 | else 146 | if v.cmd == nil then 147 | c:output(v.value) 148 | c:stop() 149 | return 150 | end 151 | c.lastBuilder = c:_createBuilder(v.cmd) 152 | c.rootBuilder = c.lastBuilder 153 | end 154 | 155 | 156 | local t2 = socket.gettime() 157 | if t2 - t1 > frameTimeout then 158 | return 159 | end 160 | end 161 | end, 162 | 163 | --private: 164 | _createBuilder = function(c, cmd) 165 | if cmd == "table" then 166 | return builderTable.new() 167 | elseif cmd == "mergeTable" then 168 | return builderMergeTable.new() 169 | end 170 | end, 171 | } 172 | end 173 | 174 | g_t.asyncRequireByStep = function (modules, depth, frameTimeout) 175 | return _{ 176 | s1 = function(c) 177 | local lanes = require("lanes") 178 | local linda = lanes.linda() 179 | c.linda = linda 180 | 181 | 182 | local function load() 183 | local index = 1 184 | local sendNum = 0 185 | local m = {} 186 | while true do 187 | local name = modules[index] 188 | if name == nil then 189 | break 190 | end 191 | m[name] = require(name) 192 | index = index + 1 193 | end 194 | 195 | local function sendTable(t, key, depth) 196 | if depth ~= nil and depth <= 0 then 197 | linda:send(0, "m", {key = key, value = t}) 198 | sendNum = (sendNum + 1) % 10 199 | return 200 | end 201 | 202 | if depth ~= nil then 203 | depth = depth - 1 204 | end 205 | 206 | linda:send(0, "m", {cmd = "table", key = key}) 207 | sendNum = (sendNum + 1) % 10 208 | for k, v in pairs(t) do 209 | if type(v) == "table" and (depth == nil or depth > 0) then 210 | local newDepth = nil 211 | if depth ~= nil then 212 | newDepth = depth - 1 213 | end 214 | sendTable(v, k, newDepth) 215 | else 216 | linda:send(0, "m", {key=k, value = v}) 217 | sendNum = (sendNum + 1) % 10 218 | end 219 | end 220 | linda:send(0, "m", {isEnd = true}) 221 | sendNum = (sendNum + 1) % 10 222 | end 223 | sendTable(m, nil, depth) 224 | end 225 | 226 | c.a = lanes.gen( "package", "table", load)() 227 | 228 | c:call(g_t.tabReceiveStreamedTable(linda, frameTimeout), "s2", {"result"}) 229 | end, 230 | } 231 | end 232 | 233 | g_t.asyncRequireFileOneByOne = function (modules, frameTimeout) 234 | return g_t.asyncRequireByStep(modules, 2, frameTimeout) 235 | end 236 | 237 | g_t.asyncRequireBigFiles = function (modules, depth, maxLines) 238 | return _{ 239 | s1 = function(c) 240 | local lanes = require("lanes") 241 | local linda = lanes.linda() 242 | c.linda = linda 243 | 244 | depth = 1 245 | maxLines = maxLines or 100 246 | 247 | c.t1 = socket.gettime() 248 | local function load() 249 | local index = 1 250 | local sendNum = 0 251 | local m = {} 252 | while true do 253 | local name = modules[index] 254 | if name == nil then 255 | break 256 | end 257 | m[name] = require(name) 258 | index = index + 1 259 | end 260 | 261 | local sendTable 262 | local sendMergeTable 263 | 264 | 265 | sendMergeTable = function (t, key) 266 | linda:send(nil, "m", {cmd = "mergeTable", key = key}) 267 | sendNum = (sendNum + 1) % 10 268 | 269 | local unit = {} 270 | 271 | local line = 0 272 | for k, v in pairs(t) do 273 | unit[k] = v 274 | line = line + 1 275 | 276 | if line >= maxLines then 277 | linda:send(nil, "m", {value = unit}) 278 | sendNum = (sendNum + 1) % 10 279 | line = 0 280 | unit = {} 281 | end 282 | end 283 | 284 | if line ~= 0 then 285 | linda:send(nil, "m", {value = unit}) 286 | sendNum = (sendNum + 1) % 10 287 | end 288 | 289 | linda:send(nil, "m", {isEnd = true}) 290 | sendNum = (sendNum + 1) % 10 291 | end 292 | 293 | sendTable = function(t, key, curDepth) 294 | linda:send(nil, "m", {cmd = "table", key = key}) 295 | sendNum = (sendNum + 1) % 10 296 | for k, v in pairs(t) do 297 | if type(v) == "table" then 298 | if curDepth + 1 >= depth then 299 | sendMergeTable(v, k) 300 | else 301 | sendTable(v, k, curDepth + 1) 302 | end 303 | else 304 | linda:send(nil, "m", {key=k, value = v}) 305 | sendNum = (sendNum + 1) % 10 306 | end 307 | end 308 | linda:send(nil, "m", {isEnd = true}) 309 | sendNum = (sendNum + 1) % 10 310 | end 311 | sendTable(m, nil, 0) 312 | end 313 | 314 | c.a = lanes.gen( "package", "table", load)() 315 | c:call(g_t.tabReceiveStreamedTable(linda, 0), "s2", {"result"}) 316 | end, 317 | } 318 | end 319 | 320 | -------------------------------------------------------------------------------- /tabClicks.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --created on August 23, 2019 5 | 6 | g_t.mouse_click_event = _{ 7 | s1 = function(c, target, callBack) 8 | if g_t.debug then 9 | c._nickName = "click<" .. (target.name or "") .. ">" 10 | end 11 | if type(target) == "table" then 12 | c.target = target:com(ct.custom_MouseEvent) 13 | else 14 | c.target = target 15 | end 16 | c.callBack = callBack 17 | c.target:AddMouseClickedListener(c.callBack) 18 | end, 19 | 20 | __addNickName = function(c) 21 | c._nickName = "mouse_click_event<" .. (c.target.name or "") .. ">" 22 | end, 23 | 24 | event = g_t.empty_event, 25 | final = function(c) 26 | c.target:RemoveMouseClickedListener(c.callBack) 27 | end, 28 | } 29 | 30 | g_t.click = _{ 31 | s1 = function(c, target, soundId, monitor) 32 | if monitor ~= nil then 33 | c.monitorRef = g_t.aliveRef(monitor) 34 | end 35 | 36 | if g_t.debug then 37 | c._nickName = "click<" .. (target.name or "") .. ">" 38 | end 39 | 40 | if type(target) == "table" then 41 | c.target = target:com(ct.button) 42 | else 43 | c.target = target 44 | end 45 | 46 | c.clickHandler = function() 47 | local monitor = nil 48 | if c.monitorRef then 49 | monitor = c.monitorRef:getTarget() 50 | end 51 | 52 | if monitor == nil or monitor:isIdle() then 53 | c:_onClick() 54 | end 55 | end 56 | 57 | c.action = CS.Utils.AddButtonListener(c.target,c.clickHandler) 58 | end, 59 | 60 | __addNickName = function(c) 61 | c._nickName = "click<" .. (c.target.name or "") .. ">" 62 | end, 63 | 64 | event = g_t.empty_event, 65 | 66 | final = function(c) 67 | c.target.onClick:RemoveListener(c.action) 68 | c.target = nil 69 | c.action = nil 70 | end, 71 | 72 | _onClick = function ( c ) 73 | -- body 74 | c:output(true) 75 | c:stop() 76 | end, 77 | } 78 | 79 | g_t.clickTextLink = _{ 80 | s1 = function(c, target, monitor) 81 | if g_t.debug then 82 | c._nickName = "click<" .. tostring(target.name) .. ">" 83 | end 84 | 85 | if monitor ~= nil then 86 | c.monitorRef = g_t.aliveRef(monitor) 87 | end 88 | 89 | c.target = target 90 | c.action = target:addTextlinkClickHandler(function(linkId, linkStr, index) 91 | local monitor = nil 92 | if c.monitorRef then 93 | monitor = c.monitorRef:getTarget() 94 | end 95 | 96 | if monitor == nil or monitor:isIdle() then 97 | c:clickLink(linkId, linkStr, index) 98 | end 99 | end) 100 | end, 101 | 102 | event = g_t.empty_event, 103 | 104 | final = function(c) 105 | c.target:removeTextlinkClickHandler(c.action) 106 | c.target = nil 107 | c.action = nil 108 | end, 109 | 110 | clickLink = function (c, linkId, linkStr, index) 111 | c:output(linkId, linkStr, index) 112 | c:stop() 113 | end, 114 | 115 | __addNickName = function(c) 116 | c._nickName = "clickTextLink<" .. (c.target.name or "") .. ">" 117 | end, 118 | } 119 | 120 | g_t.toggle = _{ 121 | s1 = function(c, target, soundId, monitor) 122 | if g_t.debug then 123 | c._nickName = "toggle<" .. (target.name or "") .. ">" 124 | end 125 | 126 | if monitor ~= nil then 127 | c.monitorRef = g_t.aliveRef(monitor) 128 | end 129 | 130 | if type(target) == "table" then 131 | c.target = target:com(ct.toggle) 132 | else 133 | c.target = target 134 | end 135 | 136 | c.toggleHandler = function(isOn) 137 | local monitor = nil 138 | if c.monitorRef then 139 | monitor = c.monitorRef:getTarget() 140 | end 141 | 142 | if monitor == nil or monitor:isIdle() then 143 | c:_onToggle(isOn) 144 | end 145 | end 146 | c.action = CS.Utils.AddToggleListener(c.target, c.toggleHandler) 147 | end, 148 | 149 | event = g_t.empty_event, 150 | 151 | final = function(c) 152 | c.target.onValueChanged:RemoveListener(c.action) 153 | c.target = nil 154 | c.action = nil 155 | end, 156 | 157 | _onToggle = function ( c , isOn) 158 | -- body 159 | c:output(isOn) 160 | if not isOn then 161 | c:call(g_t.skipFrames, "t1", nil ,1) 162 | else 163 | c:stop() 164 | end 165 | end, 166 | t2 = function(c) 167 | c:stop() 168 | end, 169 | 170 | __addNickName = function(c) 171 | c._nickName = "toggle<" .. (c.target.name or "") .. ">" 172 | end, 173 | } 174 | 175 | g_t.bookmarkPageClick = _{ 176 | s1 = function(c, target, monitor) 177 | c.target = target 178 | if monitor ~= nil then 179 | c.monitorRef = g_t.aliveRef(monitor) 180 | end 181 | 182 | c.clickHandler = function(index) 183 | local monitor = nil 184 | if c.monitorRef then 185 | monitor = c.monitorRef:getTarget() 186 | end 187 | 188 | if monitor == nil or monitor:isIdle() then 189 | c:_onClick(index) 190 | end 191 | end 192 | c.target:AddBookMarkListener(c.clickHandler) 193 | end, 194 | event = g_t.empty_event, 195 | 196 | final = function(c) 197 | c.target:RemoveBookMarkListener() 198 | c.target = nil 199 | end, 200 | 201 | _onClick = function (c, index) 202 | c:output(index) 203 | c:stop() 204 | end, 205 | } 206 | 207 | g_t.UISwitchClick = _{ 208 | s1 = function(c, uiSwitchGoTable, monitor) 209 | c.target = uiSwitchGoTable:com(ct.uiSwitch) 210 | if monitor ~= nil then 211 | c.monitorRef = g_t.aliveRef(monitor) 212 | end 213 | c.clickHandler = function(on) 214 | local monitor = nil 215 | if c.monitorRef then 216 | monitor = c.monitorRef:getTarget() 217 | end 218 | 219 | if monitor == nil or monitor:isIdle() then 220 | c:_onClick(on) 221 | end 222 | end 223 | c.target:AddValueChangeListener(c.clickHandler) 224 | end, 225 | 226 | final = function(c) 227 | c.target:RemoveValueChangeListener() 228 | c.target = nil 229 | end, 230 | 231 | _onClick = function(c, on) 232 | c:output(on) 233 | c:stop() 234 | end, 235 | 236 | event = g_t.empty_event, 237 | } 238 | 239 | g_t.toggleGroup = _{ 240 | s1 = function(c, target, soundId, monitor) 241 | if type(target) == "table" then 242 | c.target = target:com(ct.custom_toggles) 243 | else 244 | c.target = target 245 | end 246 | 247 | if g_t.debug then 248 | c._nickName = "toggleGroup<" .. c.target.name .. ">" 249 | end 250 | 251 | if monitor ~= nil then 252 | c.monitorRef = g_t.aliveRef(monitor) 253 | end 254 | 255 | c.toggleHandler = function(index) 256 | local monitor = nil 257 | if c.monitorRef then 258 | monitor = c.monitorRef:getTarget() 259 | end 260 | 261 | if monitor == nil or monitor:isIdle() then 262 | c:_onToggle(index) 263 | end 264 | end 265 | c.action = CS.Utils.AddToggleGroupListener(c.target, c.toggleHandler) 266 | end, 267 | 268 | event = g_t.empty_event, 269 | 270 | final = function(c) 271 | c.target.onValueChanged:RemoveListener(c.action) 272 | c.target = nil 273 | c.action = nil 274 | end, 275 | 276 | _onToggle = function ( c , index) 277 | c:output(index) 278 | c:stop() 279 | end, 280 | 281 | __addNickName = function(c) 282 | c._nickName = "toggleGroup<" .. (c.target.name or "") .. ">" 283 | end, 284 | } 285 | 286 | g_t.urlClick = _{ 287 | s1 = function(c, target, monitor) 288 | if type(target) == "table" then 289 | c.target = target:com(ct.text) 290 | else 291 | c.target = target 292 | end 293 | 294 | if monitor ~= nil then 295 | c.monitorRef = g_t.aliveRef(monitor) 296 | end 297 | -- if g_t.debug then 298 | c._nickName = "urlClick<" .. c.target.name .. ">" 299 | -- end 300 | c.urlHandler = CS.TMPro.TMP_TextClickEventHandler.GetObject(c.target) 301 | c.action = c.urlHandler:AddURLClickListener(function(linkID, linkText, linkIndex) 302 | local monitor = nil 303 | if c.monitorRef then 304 | monitor = c.monitorRef:getTarget() 305 | end 306 | 307 | if monitor == nil or monitor:isIdle() then 308 | c:output(linkID, linkText, linkIndex) 309 | c:stop() 310 | end 311 | end) 312 | end, 313 | event = g_t.empty_event, 314 | final = function(c) 315 | if isNil(c.urlHandler) then 316 | return 317 | end 318 | c.urlHandler:RemoveURLClickListener(c.action) 319 | end, 320 | 321 | __addNickName = function(c) 322 | c._nickName = "urlClick<" .. (c.target.name or "") .. ">" 323 | end, 324 | } 325 | 326 | 327 | g_t.dropdownClick = _{ 328 | s1 = function(c, target, monitor) 329 | if monitor ~= nil then 330 | c.monitorRef = g_t.aliveRef(monitor) 331 | end 332 | 333 | if g_t.debug then 334 | c._nickName = "dropdownClick<" .. (target.name or "") .. ">" 335 | end 336 | 337 | if type(target) == "table" then 338 | c.target = target.coms[ct.custom_toggles] 339 | else 340 | c.target = target 341 | end 342 | 343 | c.clickHandler = function(index) 344 | local monitor = nil 345 | if c.monitorRef then 346 | monitor = c.monitorRef:getTarget() 347 | end 348 | 349 | if monitor == nil or monitor:isIdle() then 350 | c:_onClick(index) 351 | end 352 | end 353 | 354 | c.target:SetValueListener(c.clickHandler) 355 | end, 356 | 357 | __addNickName = function(c) 358 | c._nickName = "click<" .. (c.target.name or "") .. ">" 359 | end, 360 | 361 | event = g_t.empty_event, 362 | 363 | final = function(c) 364 | c.target.onValueChanged:RemoveAllListeners() 365 | c.target = nil 366 | end, 367 | 368 | _onClick = function(c, index) 369 | -- body 370 | c:output(index) 371 | c:stop() 372 | end, 373 | } 374 | 375 | 376 | -------------------------------------------------------------------------------- /tabSerialization.lua: -------------------------------------------------------------------------------- 1 | 2 | --author cs 3 | --email 04nycs@gmail.com 4 | -- 5 | --https://github.com/ThinEureka/tabMachine 6 | --created on Oct 26, 2023 7 | -- 8 | -- 9 | local tabSerialization = {} 10 | 11 | local baseExcludeKeys = { 12 | __tab = true, 13 | __type = true, 14 | __address = true, 15 | } 16 | 17 | local raw_pairs = function(x) 18 | return next, x, nil 19 | end 20 | 21 | local rawget = rawget 22 | 23 | function tabSerialization.createSnapshot(rootContext, detailControl) 24 | local addPath = false 25 | local addStat = nil 26 | local addTableAddress = false 27 | 28 | local excludeKeys = baseExcludeKeys 29 | 30 | if detailControl ~= nil then 31 | addPath = detailControl.addPath 32 | addTableAddress = detailControl.addTableAddress 33 | local extraExcludeKeys = detailControl.extraExcludeKeys 34 | if extraExcludeKeys ~= nil then 35 | excludeKeys = {} 36 | for k, v in pairs(baseExcludeKeys) do 37 | excludeKeys[k] = v 38 | end 39 | 40 | for k, v in pairs(extraExcludeKeys) do 41 | excludeKeys[k] = v 42 | end 43 | end 44 | 45 | addStat = detailControl.addStat 46 | end 47 | 48 | local addressToTable = {} 49 | local tableToAddress = {} 50 | 51 | local addressToImage = {} 52 | local addressToPath = {} 53 | 54 | local nextAddress = 1 55 | local visitArray = {} 56 | local visitMap = {} 57 | 58 | local function createImage(t, type, path) 59 | local address = tableToAddress[t] 60 | if address ~= nil then 61 | local image = addressToImage[address] 62 | return address, image 63 | end 64 | 65 | local address = nextAddress 66 | nextAddress = nextAddress + 1 67 | tableToAddress[t] = address 68 | addressToTable[address] = t 69 | addressToPath[address] = path 70 | 71 | local image = {} 72 | if type == "table" then 73 | if addTableAddress then 74 | image.__type = type 75 | image.__address = address 76 | end 77 | else 78 | image.__type = type 79 | image.__address = address 80 | end 81 | 82 | if addPath then 83 | image.__path = path 84 | end 85 | addressToImage[address] = image 86 | 87 | return address, image 88 | end 89 | 90 | --create image of contexts first 91 | local contextArray = {} 92 | table.insert(contextArray, rootContext) 93 | local contextIndex = 1 94 | while contextIndex <= #contextArray do 95 | local c = contextArray[contextIndex] 96 | local address, image = createImage(c, "table", c:_getPath()) 97 | local subContexts = c.__subContexts 98 | if subContexts ~= nil then 99 | for _, sc in ipairs(subContexts) do 100 | if not sc.__excludeInSnapshot then 101 | table.insert(contextArray, sc) 102 | end 103 | end 104 | end 105 | contextIndex = contextIndex + 1 106 | end 107 | 108 | local function visitTable(t, image, address, path) 109 | for k, v in raw_pairs(t) do 110 | local k_type = type(k) 111 | if k_type == "string" or k_type == "number" then 112 | if excludeKeys[k] == nil then 113 | local v_type = type(v) 114 | if v_type == "number" or v_type == "string" or v_type == "boolean" then 115 | image[k] = v 116 | elseif v_type == "table" then 117 | local v_path = nil 118 | if addPath then 119 | v_path = path .. "@" .. k 120 | end 121 | local v_address, v_image = createImage(v, v_type, v_path) 122 | image[k] = v_image 123 | if not rawget(v, "__excludeInSnapshot") then 124 | if not visitMap[v_address] then 125 | visitMap[v_address] = true 126 | table.insert(visitArray, v_address) 127 | end 128 | else 129 | v_image.__name = rawget(v, "__name") 130 | end 131 | else 132 | if addPath then 133 | v_path = path .. "@" .. k 134 | end 135 | local v_address, v_image = createImage(v, v_type, v_path) 136 | image[k] = v_image 137 | end 138 | end 139 | end 140 | end 141 | end 142 | 143 | local rootAddress = tableToAddress[rootContext] 144 | visitMap[rootAddress] = true 145 | table.insert(visitArray, rootAddress) 146 | local visitIndex = 1 147 | 148 | while visitIndex <= #visitArray do 149 | local nextA = visitArray[visitIndex] 150 | local nextImage = addressToImage[nextA] 151 | local nextT = addressToTable[nextA] 152 | local nextPath = addressToPath[nextA] 153 | visitTable(nextT, nextImage, nextA, nextPath) 154 | visitIndex = visitIndex + 1 155 | end 156 | 157 | local rootImage = addressToImage[rootAddress] 158 | 159 | if addStat then 160 | if addStat.statTreeSize then 161 | tabSerialization.statTreeSize(rootImage) 162 | end 163 | end 164 | 165 | local snapshot = rootImage 166 | return snapshot 167 | end 168 | 169 | function tabSerialization.statTreeSize(context) 170 | local treeSize = 1 171 | local subContexts = context.__subContexts 172 | 173 | if subContexts ~= nil then 174 | for _, sc in ipairs(subContexts) do 175 | if not sc.__excludeInSnapshot then 176 | treeSize = treeSize + tabSerialization.statTreeSize(sc) 177 | end 178 | end 179 | end 180 | 181 | context.__treeSize = treeSize 182 | return treeSize 183 | 184 | end 185 | 186 | function tabSerialization.createTabTreeFromSnapshot(snapshot) 187 | do 188 | return snapshot 189 | end 190 | -- local addressToTable = {} 191 | -- local addressToImage = snapshot.addressToImage 192 | -- 193 | -- local visitArray = {} 194 | -- local visitMap = {} 195 | -- 196 | -- local function createTable(address, type) 197 | -- local t = addressToTable[address] 198 | -- if t ~= nil then 199 | -- return t 200 | -- end 201 | -- 202 | -- t = {} 203 | -- addressToTable[address] = t 204 | -- 205 | -- if type ~= nil then 206 | -- t["#type"] = type 207 | -- t["#address"] = address 208 | -- end 209 | -- 210 | -- return t 211 | -- end 212 | -- 213 | -- local function visit(t, image, address) 214 | -- for k, v in pairs(image) do 215 | -- if excludeKeys[k] == nil then 216 | -- local v_type = type(v) 217 | -- if v_type == "number" or v_type == "string" or v_type == "boolean" then 218 | -- t[k] = v 219 | -- elseif v_type == "table" then 220 | -- if v.__type == "table" then 221 | -- local v_address = v.__address 222 | -- t[k] = createTable(v_address) 223 | -- if not visitMap[v_address] then 224 | -- visitMap[v_address] = true 225 | -- table.insert(visitArray, v_address) 226 | -- end 227 | -- else 228 | -- t[k] = createTable(v.__address, v.__type) 229 | -- end 230 | -- end 231 | -- end 232 | -- end 233 | -- end 234 | -- 235 | -- local rootAddress = snapshot.rootAddress 236 | -- local rootContext = createTable(rootAddress) 237 | -- local rootImage = addressToImage[rootAddress] 238 | -- 239 | -- local nextT = rootContext 240 | -- local nextImage = rootImage 241 | -- local nextA = rootAddress 242 | -- 243 | -- visitMap[rootAddress] = true 244 | -- 245 | -- local visitIndex = 1 246 | -- while true do 247 | -- visit(nextT, nextImage, nextA) 248 | -- nextA = visitArray[visitIndex] 249 | -- visitIndex = visitIndex + 1 250 | -- 251 | -- if nextA then 252 | -- nextT = addressToTable[nextA] 253 | -- nextImage = addressToImage[nextA] 254 | -- else 255 | -- break 256 | -- end 257 | -- end 258 | -- 259 | -- return rootContext 260 | end 261 | 262 | local function exportstring( s ) 263 | return string.format("%q", s) 264 | end 265 | 266 | function tabSerialization.saveSnapshotToFile(snapshot, filename) 267 | local charS,charE = " ","\n" 268 | local file,err = io.open( filename, "wb" ) 269 | if err then return err end 270 | 271 | -- initiate variables for save procedure 272 | local tables,lookup = { snapshot },{ [snapshot] = 1 } 273 | file:write( "return {"..charE ) 274 | 275 | for idx,t in ipairs( tables ) do 276 | file:write( "-- Table: {"..idx.."}"..charE ) 277 | file:write( "{"..charE ) 278 | local thandled = {} 279 | 280 | for i,v in ipairs( t ) do 281 | thandled[i] = true 282 | local stype = type( v ) 283 | -- only handle value 284 | if stype == "table" then 285 | if not lookup[v] then 286 | table.insert( tables, v ) 287 | lookup[v] = #tables 288 | end 289 | file:write( charS.."{"..lookup[v].."},"..charE ) 290 | elseif stype == "string" then 291 | file:write( charS..exportstring( v )..","..charE ) 292 | elseif stype == "number" then 293 | file:write( charS..tostring( v )..","..charE ) 294 | end 295 | end 296 | 297 | for i,v in pairs( t ) do 298 | -- escape handled values 299 | if (not thandled[i]) then 300 | 301 | local str = "" 302 | local stype = type( i ) 303 | -- handle index 304 | if stype == "table" then 305 | if not lookup[i] then 306 | table.insert( tables,i ) 307 | lookup[i] = #tables 308 | end 309 | str = charS.."[{"..lookup[i].."}]=" 310 | elseif stype == "string" then 311 | str = charS.."["..exportstring( i ).."]=" 312 | elseif stype == "number" then 313 | str = charS.."["..tostring( i ).."]=" 314 | end 315 | 316 | if str ~= "" then 317 | stype = type( v ) 318 | -- handle value 319 | if stype == "table" then 320 | if not lookup[v] then 321 | table.insert( tables,v ) 322 | lookup[v] = #tables 323 | end 324 | file:write( str.."{"..lookup[v].."},"..charE ) 325 | elseif stype == "string" then 326 | file:write( str..exportstring( v )..","..charE ) 327 | elseif stype == "number" then 328 | file:write( str..tostring( v )..","..charE ) 329 | end 330 | end 331 | end 332 | end 333 | file:write( "},"..charE ) 334 | end 335 | file:write( "}" ) 336 | local fileSize = file:seek() 337 | file:close() 338 | return fileSize 339 | end 340 | 341 | function tabSerialization.loadSnapshotFromFile(sfile) 342 | local ftables,err = loadfile( sfile ) 343 | if err then return _,err end 344 | local tables = ftables() 345 | for idx = 1,#tables do 346 | local tolinki = {} 347 | for i,v in pairs( tables[idx] ) do 348 | if type( v ) == "table" then 349 | tables[idx][i] = tables[v[1]] 350 | end 351 | if type( i ) == "table" and tables[i[1]] then 352 | table.insert( tolinki,{ i,tables[i[1]] } ) 353 | end 354 | end 355 | -- link indices 356 | for _,v in ipairs( tolinki ) do 357 | tables[idx][v[2]],tables[idx][v[1]] = tables[idx][v[1]],nil 358 | end 359 | end 360 | return tables[1] 361 | end 362 | 363 | function tabSerialization.addSnapshotTabTree(parentContext, tabTree, name) 364 | local container = nil 365 | if not parentContext:hasSub("__snapshots") then 366 | container = parentContext:call(g_t.tabContainer, "__snapshots") 367 | container.__excludeInSnapshot = true 368 | container.__subContexts = {} 369 | else 370 | container = parentContext:getSub("__snapshots") 371 | end 372 | 373 | tabTree.__name = name 374 | table.insert(container.__subContexts, tabTree) 375 | end 376 | 377 | 378 | tabSerialization.tabRecordTabClip = _{ 379 | tabName = "recordSnapshots", 380 | 381 | s1 = function (c, rootContext, totalFrame, frameInterval) 382 | -- c.__excludeInSnapshot = true 383 | 384 | c.rootContext = rootContext 385 | c.totalFrame = totalFrame 386 | c.frameInterval = frameInterval or 1 387 | 388 | local clip = {} 389 | clip.snapshots = {} 390 | c.clip = clip 391 | -- c.clip.__excludeInSnapshot = true 392 | c.beginFrame = g_frameIndex 393 | 394 | c:s1_update() 395 | end, 396 | 397 | s1_update = function(c) 398 | local frameIndex = g_frameIndex 399 | local frameOffset = frameIndex - c.beginFrame 400 | local clip = c.clip 401 | local snapshots = clip.snapshots 402 | 403 | if frameOffset % c.frameInterval == 0 then 404 | local snapshot = tabSerialization.createSnapshot(c.rootContext) 405 | snapshot.__excludeInSnapshot = true 406 | snapshot.frameIndex = frameIndex 407 | table.insert(snapshots, snapshot) 408 | end 409 | 410 | if c.totalFrame ~= nil then 411 | if #snapshots >= c.totalFrame then 412 | c:stop() 413 | end 414 | end 415 | end, 416 | 417 | final = function(c) 418 | c:output(c.clip) 419 | end, 420 | 421 | } 422 | 423 | return tabSerialization 424 | -------------------------------------------------------------------------------- /tabSocket.lua: -------------------------------------------------------------------------------- 1 | --tabSocket is a simple tab interface for lua socket, the implementation 2 | --demonstrates the correct use of tabProxy 3 | --author cs 4 | --email 04nycs@gmail.com 5 | --https://github.com/ThinEureka/tabMachine 6 | --created on Jan 11, 2021 7 | 8 | local socket = require "socket" 9 | 10 | local tabSocket = nil 11 | 12 | local STATUS_CLOSED = "closed" 13 | local STATUS_NOT_CONNECTED = "Socket is not connected" 14 | local STATUS_ALREADY_CONNECTED = "already connected" 15 | local STATUS_ALREADY_IN_PROGRESS = "Operation already in progress" 16 | local STATUS_TIMEOUT = "timeout" 17 | 18 | -------------------------------------------------------------------------------- 19 | -- main flow 20 | -- private: 21 | tabSocket = _{ 22 | tabName = "tabSocket", 23 | 24 | STATE = { 25 | DISCONNECTED = 1, 26 | CONNECTING = 2, 27 | CONNECTED = 3, 28 | }, 29 | 30 | s1 = function(c, isUsingIpV6) 31 | c._isUsingIpV6 = isUsingIpV6 32 | c._closeDelay = 0.1 33 | c._tcp = nil 34 | c._buffer = nil 35 | 36 | c._state = tabSocket.STATE.DISCONNECTED 37 | c._nickName = "SOCKET->等待连接指令" 38 | c:suspend("s2") 39 | end, 40 | 41 | s2 = function(c, mode, ip, port, timeout, tcp) 42 | c._nickName = tostring(ip)..":"..tostring(port) 43 | 44 | if mode == 1 then 45 | c._state = tabSocket.STATE.CONNECTING 46 | c._nickName = "SOCKET->连接中" 47 | c._buffer = {} 48 | c._tcp = nil 49 | c:call(tabSocket.tabConnectting(ip, port, timeout) >> "isSuccess" >> "err", "s3") 50 | else 51 | --only for server mode 52 | c._nickName = "SOCKET->已连接" 53 | c._tcp = tcp 54 | c._tcp:settimeout(0.000000000001) 55 | c._buffer = {} 56 | c._state = tabSocket.STATE.CONNECTED 57 | c._connected = c:call(tabSocket.tabConnectted(), "s5") 58 | end 59 | end, 60 | 61 | s4 = function(c) 62 | if c.isSuccess then 63 | c._nickName = "SOCKET->已连接" 64 | c._state = tabSocket.STATE.CONNECTED 65 | c._connected = c:call(tabSocket.tabConnectted(), "s5") 66 | else 67 | c:start("s1") 68 | end 69 | end, 70 | 71 | s6 = function(c) 72 | c._connected = nil 73 | c:start("s1") 74 | end, 75 | 76 | final = function(c) 77 | if c._tcp then 78 | c._tcp:close() 79 | end 80 | end, 81 | 82 | -- inner 83 | inner = { 84 | tabSocket = function(c) 85 | return c 86 | end, 87 | 88 | buffer = function(c) 89 | return c._buffer 90 | end, 91 | 92 | isUsingIpV6 = function(c) 93 | return c._isUsingIpV6 94 | end, 95 | 96 | tcp = function(c) 97 | return c._tcp 98 | end, 99 | 100 | setTcp = function(c, tcp) 101 | c._tcp = tcp 102 | end, 103 | 104 | closeDelay = function(c) 105 | return c._closeDelay 106 | end, 107 | }, 108 | 109 | --public: 110 | connect = function(c, ip, port, timeout) 111 | c:resume("s2", 1, ip, port, timeout) 112 | return c:tabProxy("s3") 113 | end, 114 | 115 | disconnect = function(c) 116 | c:notify("tabSocket.disconnect") 117 | end, 118 | 119 | --for server 120 | acceptSocket = function(c, tcp) 121 | assert(tcp ~= nil) 122 | assert(c._state == tabSocket.STATE.DISCONNECTED) 123 | c:resume("s2", 2, nil, nil, nil, tcp) 124 | end, 125 | 126 | send = function(c, data) 127 | if c._connected == nil then 128 | return STATUS_NOT_CONNECTED 129 | end 130 | 131 | return c._connected:send(data) 132 | end, 133 | 134 | getState = function(c) 135 | return c._state 136 | end, 137 | 138 | --the progress when socket is the specified state 139 | tabInState = _{ 140 | s1 = function(c, self, state) 141 | c._nickName = "tabSocket#tabInState->".. state 142 | if self._state ~= state then 143 | c:stop() 144 | else 145 | c:call(self:tabState(state), "s2") 146 | end 147 | end, 148 | }, 149 | 150 | --private: 151 | tabState = function(c, scName) 152 | if scName == tabSocket.STATE.CONNECTING then 153 | return c:tabProxy("s2") 154 | elseif scName == tabSocket.STATE.CONNECTED then 155 | return c:tabProxy("s5") 156 | elseif scName == tabSocket.STATE.DISCONNECTED then 157 | return c:tabSuspend("s2") 158 | end 159 | end, 160 | } 161 | 162 | --private: 163 | -- 2nd level flow 164 | tabSocket.tabConnectting = _{ 165 | tabName = "tabConnectting", 166 | 167 | s1 = function(c, ip, port, timeout) 168 | if timeout == nil then 169 | timeout = 3 170 | end 171 | 172 | c.timeout = timeout 173 | c.port = port 174 | 175 | local ipV6 = tabSocket.getIpV6Address(ip) 176 | if c:_("isUsingIpV6") and ipV6 ~= nil then 177 | c.ip = ipV6 178 | c.tcp = socket.tcp6() 179 | else 180 | c.ip = ip 181 | c.tcp = socket.tcp() 182 | end 183 | 184 | c.tcp:settimeout(0.000000000001) 185 | c:_("setTcp", c.tcp) 186 | c.time = 0 187 | 188 | c.isConnected = c:_tryToConnect() 189 | if c.isConnected then 190 | c:output(true) 191 | c:stop("s1") 192 | end 193 | end, 194 | 195 | s1_update = function(c, dt) 196 | c.isConnected = c:_tryToConnect() 197 | if c.isConnected then 198 | c:output(true) 199 | c:stop() 200 | return 201 | end 202 | 203 | c.time = c.time + dt 204 | if c.time >= c.timeout then 205 | c:output(false, "tabSocket.connectting.timeout") 206 | c:stop() 207 | end 208 | end, 209 | 210 | s2 = function(c) 211 | c:call(g_t.skipFrames(1), "s3") 212 | end, 213 | 214 | s4 = function(c) 215 | c:stop() 216 | end, 217 | 218 | event = { 219 | ["tabSocket.disconnect"] = function(c) 220 | c:output(false, "tabSocket.disconnect") 221 | c:stop() 222 | end 223 | }, 224 | 225 | final = function(c) 226 | if not c.isConnected then 227 | c.tcp:close() 228 | c.tcp = nil 229 | end 230 | end, 231 | 232 | --private: 233 | _tryToConnect = function(c) 234 | local isSuccess, status = c.tcp:connect(c.ip, c.port) 235 | if not isSuccess then 236 | if status == STATUS_ALREADY_CONNECTED then 237 | isSuccess = true 238 | end 239 | end 240 | 241 | return isSuccess 242 | end, 243 | } 244 | 245 | tabSocket.tabConnectted = _{ 246 | tabName = "tabConnectted", 247 | 248 | s1 = function(c) 249 | c._sendQueue = {} 250 | end, 251 | 252 | s1_update = function(c) 253 | local body, status, partical = c:_("tcp"):receive("*a") -- read the package body 254 | if partical and partical:len() > 0 then 255 | local buffer = c:_("buffer") 256 | table.insert(buffer, partical) 257 | else 258 | if body and body:len() > 0 then 259 | local buffer = c:_("buffer") 260 | table.insert(buffer, body) 261 | end 262 | end 263 | 264 | if status == STATUS_CLOSED or status == STATUS_NOT_CONNECTED then 265 | c.status = status 266 | c:stop("s1") 267 | return 268 | end 269 | 270 | local queueSize = #c._sendQueue 271 | while queueSize > 0 do 272 | local data = table.remove(c._sendQueue, 1) 273 | c:send(data, true) 274 | if queueSize == #c._sendQueue then 275 | break 276 | end 277 | queueSize = #c._sendQueue 278 | end 279 | end, 280 | 281 | s2 = function(c) 282 | -- 在socket连接关闭后,给个默认的延迟是外部有一定的时间读取buffer里内容 283 | c:call(g_t.delay, "s3", nil, c:_("closeDelay")) 284 | end, 285 | 286 | s4 = function(c) 287 | c:output(false, c.status) 288 | c:stop() 289 | end, 290 | 291 | event = { 292 | ["tabSocket.disconnect"] = function(c) 293 | c:output(false, "tabSocket.disconnect") 294 | c:stop() 295 | end 296 | }, 297 | 298 | final = function(c) 299 | local tcp = c:_("tcp") 300 | tcp:shutdown() 301 | tcp:close() 302 | c:_("setTcp", nil) 303 | end, 304 | 305 | -- public: 306 | send = function(c, data, isRent) 307 | if not isRent then 308 | if #c._sendQueue > 0 then 309 | table.insert(c._sendQueue, data) 310 | return nil, nil, #data 311 | end 312 | end 313 | 314 | local size, err, sent = c:_("tcp"):send(data) 315 | 316 | if err == nil then 317 | return size, nil, nil 318 | elseif err == "timeout" then 319 | table.insert(c._sendQueue, 1 , data:sub(sent+1)) 320 | return nil, nil, sent 321 | else 322 | --assert(false) 323 | return nil, err, sent 324 | end 325 | end, 326 | } 327 | 328 | -------------------------------------------------------------------------------- 329 | --public: 330 | 331 | --read a segment of which the boundary is determined by funDecode 332 | tabSocket.tabReadOneSegment = _{ 333 | tabName = "tabSocket#tabReadOneSegment", 334 | 335 | s1 = function(c, self, funDecode, updateInterval, timeout, disconnectWhenTimeout) 336 | c.buffer = self:_("buffer") 337 | c.tabSocket = self 338 | 339 | c.funDecode = funDecode 340 | c.disconnectWhenTimeout = disconnectWhenTimeout 341 | 342 | c.lastBuffLen = 0 343 | local segment = c:_decode(c.buffer, c.funDecode) 344 | if segment ~= nil then 345 | c:output(segment) 346 | c:stop() 347 | end 348 | 349 | if timeout then 350 | c:call(g_t.delay, "d1", nil, timeout) 351 | end 352 | 353 | c:setDynamics("s2", "updateInterval", updateInterval) 354 | end, 355 | 356 | s2 = g_t.empty_fun, 357 | 358 | s2_update = function(c, dt) 359 | local buffer = c.buffer 360 | local newBuffLen = #buffer 361 | local funDecode = c.funDecode 362 | if newBuffLen ~= c.lastBuffLen and newBuffLen > 0 then 363 | local segment = c:_decode(buffer, funDecode) 364 | if segment ~= nil then 365 | c:output(segment) 366 | c:stop() 367 | return 368 | end 369 | c.lastBuffLen = #buffer 370 | end 371 | end, 372 | 373 | --override by setDynamics 374 | s2_updateInterval = nil, 375 | 376 | d2 = function(c) 377 | if c.disconnectWhenTimeout then 378 | c:stop("s2") 379 | c.tabSocket:disconnect() 380 | else 381 | c:output(nil) 382 | c:stop() 383 | end 384 | end, 385 | 386 | event = g_t.empty_event, 387 | 388 | -- private: 389 | _decode = function(c, buffer, funDecode) 390 | local newBuffLen = #buffer 391 | if newBuffLen > 0 then 392 | local stream 393 | if newBuffLen > 1 then 394 | stream = table.concat(buffer) 395 | else 396 | stream = buffer[1] 397 | end 398 | 399 | local segment, remain = funDecode(stream) 400 | 401 | while (#buffer > 0) do 402 | table.remove(buffer) 403 | end 404 | 405 | if remain and remain:len() > 0 then 406 | table.insert(buffer, remain) 407 | end 408 | 409 | return segment 410 | else 411 | return nil 412 | end 413 | end, 414 | } 415 | 416 | --pull msgs repeatly 417 | tabSocket.tabPullSegments = _{ 418 | tabName = "tabSocket#tabPullSegments", 419 | 420 | s1 = function(c, self, funDecode, segmentHandler, updateInterval) 421 | c.funDecode = funDecode 422 | c.buffer = self:_("buffer") 423 | c.segmentHandler = segmentHandler 424 | c.lastBuffLen = 0 425 | c.lastBreak = false 426 | c:setDynamics("s2", "updateInterval", updateInterval) 427 | end, 428 | 429 | s2 = function(c) 430 | c:_pullSegment() 431 | end, 432 | 433 | s2_update = function(c, dt) 434 | c:_pullSegment() 435 | end, 436 | 437 | --override by setDynamics 438 | s2_updateInterval = nil, 439 | 440 | --private: 441 | _pullSegment = function(c, frameTime) 442 | local buffer = c.buffer 443 | local newBuffLen = #buffer 444 | local socket = require("socket") 445 | local startTime = socket:gettime() 446 | if not frameTime then 447 | frameTime = 0.001 448 | end 449 | if (newBuffLen ~= c.lastBuffLen and newBuffLen > 0) or c.lastBreak then 450 | c.lastBreak = false 451 | local funDecode = c.funDecode 452 | local segmentHandler = c.segmentHandler 453 | while true do 454 | local segment = c:_decode(buffer, funDecode) 455 | if segment ~= nil then 456 | segmentHandler(segment) 457 | else 458 | break 459 | end 460 | local endTime = socket:gettime() 461 | if (endTime - startTime > frameTime) then 462 | c.lastBreak = true 463 | break 464 | end 465 | end 466 | c.lastBuffLen = #buffer 467 | end 468 | end, 469 | 470 | _decode = function(c, buffer, funDecode) 471 | local newBuffLen = #buffer 472 | if newBuffLen > 0 then 473 | local stream 474 | if newBuffLen > 1 then 475 | stream = table.concat(buffer) 476 | else 477 | stream = buffer[1] 478 | end 479 | 480 | local segment, remain = funDecode(stream) 481 | 482 | while (#buffer > 0) do 483 | table.remove(buffer) 484 | end 485 | 486 | if remain and remain:len() > 0 then 487 | table.insert(buffer, remain) 488 | return segment 489 | end 490 | 491 | return segment 492 | else 493 | return nil 494 | end 495 | end, 496 | } 497 | 498 | 499 | -------------------------------------------------------------------------------- 500 | -- static private: 501 | tabSocket.getIpV6Address = function(ip) 502 | local result = socket.dns.getaddrinfo(ip) 503 | local addr = nil 504 | if result then 505 | for k,v in pairs(result) do 506 | if v.family == "inet6" then 507 | addr = v.addr 508 | break 509 | end 510 | end 511 | end 512 | 513 | return addr 514 | end 515 | 516 | return tabSocket 517 | 518 | -------------------------------------------------------------------------------- /tabQueue.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --created on July 18, 2020 5 | 6 | local tabQueue = nil 7 | 8 | -- tabQueue currently does not support bind tab 9 | tabQueue = _{ 10 | tabName = "tabQueue", 11 | 12 | DUPLICATE_ACTION = { 13 | NONE = 0, 14 | REPLACE = 1, 15 | IGNORE = 2, 16 | MERGE = 3, 17 | }, 18 | 19 | 20 | s1 = function(c, firstRunInFrame) 21 | c._queue = {} 22 | c._uid = 0 23 | c.isOpen = false 24 | c.firstRunInFrame = firstRunInFrame 25 | c._curAliveMap = c:call(g_t.tabAliveMap, "curTabMap") 26 | c._curTabConfigMap = {} 27 | end, 28 | 29 | s2 = function(c) 30 | if c.isOpen then 31 | local index = #c._queue 32 | while index > 0 do 33 | local cfg = c._queue[index] 34 | if c:checkCanRun(cfg) then 35 | table.remove(c._queue, index) 36 | local context = c:call(c._tabRunning(cfg), "s3") 37 | c:notify("runningTab", cfg.uid, context:getTarget()) 38 | if not context:isStopped() then 39 | c:start("s2") 40 | end 41 | return 42 | end 43 | index = index - 1 44 | end 45 | end 46 | end, 47 | 48 | s4 = function(c) 49 | c:start("s2") 50 | end, 51 | 52 | event = g_t.empty_event, 53 | 54 | inner = { 55 | tabQueue = function(c) 56 | return c 57 | end, 58 | 59 | getTabUid = function(c) 60 | c._uid = c._uid + 1 61 | return c._uid 62 | end, 63 | 64 | removeByUid = function(c, uid) 65 | -- 移除队列里面的对象 66 | local index = 1 67 | while index <= #c._queue do 68 | local data = c._queue[index] 69 | if data.uid == uid then 70 | table.remove(c._queue, index) 71 | c:notify("removeTab", data.uid) 72 | return 73 | else 74 | index = index + 1 75 | end 76 | end 77 | end, 78 | 79 | insertCurTab = function(c, config, tab) 80 | local uid = config.uid 81 | c._curAliveMap:set(uid, tab) 82 | c._curTabConfigMap[uid] = config 83 | end, 84 | 85 | removeCurTab = function(c, uid) 86 | c._curAliveMap:remove(uid) 87 | c._curTabConfigMap[uid] = nil 88 | end, 89 | }, 90 | 91 | --private: 92 | startNextTask = function(c) 93 | c:start("s2") 94 | end, 95 | 96 | getCurTasks = function(c) 97 | local tabs = {} 98 | c._curAliveMap:forEach(function (t) 99 | table.insert(tabs, t) 100 | end) 101 | return tabs 102 | end, 103 | 104 | checkCanRun = function(c, checkCfg) 105 | c._curAliveMap:validate() 106 | for uid, curRunningCfg in pairs(c._curTabConfigMap) do 107 | local tab = c._curAliveMap:get(uid) 108 | if tab == nil then 109 | c._curTabConfigMap[uid] = nil 110 | else 111 | local pass = c:_verifyPass(checkCfg, curRunningCfg) 112 | if not pass then 113 | return false 114 | end 115 | end 116 | end 117 | return true 118 | end, 119 | 120 | _verifyPass = function(c, verifyCfg, curRunningCfg) 121 | if verifyCfg.unblockType == nil then 122 | return false 123 | end 124 | if curRunningCfg.unblockList == nil then 125 | return false 126 | end 127 | for _, v in ipairs(curRunningCfg.unblockList) do 128 | if verifyCfg.unblockType == v then 129 | return true 130 | end 131 | end 132 | return false 133 | end, 134 | 135 | _tabRunning = _{ 136 | s1 = function(c, config) 137 | c.cfg = config 138 | c._nickName = "running_"..c.cfg.uid 139 | c.targetTab = c:call(c.cfg.tab(c.cfg.tabParams), "s2") 140 | if c.targetTab:isStopped() then 141 | c.noRemove = true 142 | c:stop() 143 | return 144 | end 145 | c:_("insertCurTab", c.cfg, c.targetTab) 146 | end, 147 | 148 | final = function(c) 149 | if c.noRemove or c:_("tabQueue"):isQuitting() then 150 | return 151 | end 152 | c:_("removeCurTab", c.cfg.uid) 153 | end, 154 | 155 | getTarget = function(c) 156 | return c.targetTab 157 | end, 158 | } 159 | } 160 | 161 | 162 | tabQueue.tabStartTaskNextFrame =_{ 163 | s1 = function(c) 164 | c:call(g_t.skipFrames(1), "s2") 165 | end, 166 | 167 | s3 = function(c) 168 | local queue = c:_("tabQueue") 169 | if #queue:getCurTasks() <= 0 then 170 | queue:startNextTask() 171 | end 172 | end 173 | } 174 | 175 | --public: 176 | function tabQueue:add(tab, tabParams, isWait, queueParams) 177 | 178 | local queueData = { 179 | uid = self:_("getTabUid"), 180 | tab = #tab, 181 | -- tab的参数 182 | tabParams = tabParams, 183 | -- 优先级,如果没有传递优先级,统统默认-1 184 | priority = (queueParams and queueParams.priority or tab.priority) or -1, 185 | -- 被放行类型 186 | unblockType = queueParams and queueParams.unblockType or tab.unblockType, 187 | -- 放行列表 188 | unblockList = queueParams and queueParams.unblockList or tab.unblockList, 189 | duplicateTag = queueParams and queueParams.duplicateTag or tab.duplicateTag, 190 | duplicateAction = queueParams and queueParams.duplicateAction or tab.duplicateAction, 191 | dontStopHostWhenStop = queueParams and queueParams.dontStopHostWhenStop 192 | } 193 | 194 | local replaceFlag = false 195 | if queueData.duplicateAction ~= nil and 196 | queueData.duplicateAction ~= tabQueue.DUPLICATE_ACTION.NONE then 197 | for index, old in ipairs(self._queue) do 198 | if old.duplicateTag == queueData.duplicateTag then 199 | if queueData.duplicateAction == tabQueue.DUPLICATE_ACTION.IGNORE then 200 | return nil, 0 201 | elseif queueData.duplicateAction == tabQueue.DUPLICATE_ACTION.MERGE 202 | or queueData.duplicateAction == tabQueue.DUPLICATE_ACTION.REPLACE then 203 | 204 | if tab.duplicateAction == tabQueue.DUPLICATE_ACTION.MERGE then 205 | queueData.tab = #tab.merge(tab, old.tab) 206 | end 207 | 208 | if queueData.priority == old.priority then 209 | replaceFlag = true 210 | self._queue[index] = queueData 211 | break 212 | end 213 | 214 | table.remove(self._queue, index) 215 | self:notify("removeTab", old.uid) 216 | break 217 | end 218 | end 219 | end 220 | end 221 | 222 | if not replaceFlag then 223 | self:_addToQueueByPriority(queueData) 224 | end 225 | 226 | local waitTab = nil 227 | if isWait then 228 | waitTab = self:waitTabCompleted(queueData.uid, queueData.dontStopHostWhenStop) 229 | end 230 | 231 | if self.firstRunInFrame or self:checkCanRun(queueData) then 232 | self:startNextTask() 233 | elseif not self:getSub("tabStartTaskNextFrame") then 234 | self:call(tabQueue.tabStartTaskNextFrame(), "tabStartTaskNextFrame") 235 | end 236 | return waitTab, queueData.uid 237 | end 238 | 239 | function tabQueue:addNow(tab, tabParams, queueParams) 240 | local queueData = { 241 | tab = #tab, 242 | -- tab的参数 243 | tabParams = tabParams, 244 | -- 优先级,如果没有传递优先级,统统默认-1 245 | priority = (queueParams and queueParams.priority or tab.priority) or -1, 246 | -- 被放行类型 247 | unblockType = queueParams and queueParams.unblockType or tab.unblockType, 248 | -- 放行列表 249 | unblockList = queueParams and queueParams.unblockList or tab.unblockList, 250 | duplicateTag = queueParams and queueParams.duplicateTag or tab.duplicateTag, 251 | duplicateAction = queueParams and queueParams.duplicateAction or tab.duplicateAction, 252 | dontStopHostWhenStop = queueParams and queueParams.dontStopHostWhenStop, 253 | uid = self:_("getTabUid") 254 | } 255 | self:call(self._tabRunning(queueData), "s3") 256 | return queueData.uid 257 | end 258 | 259 | function tabQueue:removeAllByTag(tag) 260 | local index = 1 261 | while index <= #self._queue do 262 | local data = self._queue[index] 263 | if data.duplicateTag == tag then 264 | table.remove(self._queue, index) 265 | self:notify("removeTab", data.uid) 266 | else 267 | index = index + 1 268 | end 269 | end 270 | end 271 | 272 | function tabQueue:removeByFilter(filter) 273 | local index = 1 274 | while index <= #self._queue do 275 | local data = self._queue[index] 276 | if filter(data.tab) then 277 | table.remove(self._queue, index) 278 | self:notify("removeTab", data.uid) 279 | else 280 | index = index + 1 281 | end 282 | end 283 | end 284 | 285 | function tabQueue:remove(tab) 286 | local index = 1 287 | while index <= #self._queue do 288 | local data = self._queue[index] 289 | if data.tab == tab then 290 | table.remove(self._queue, index) 291 | self:notify("removeTab", data.uid) 292 | break 293 | else 294 | index = index + 1 295 | end 296 | end 297 | end 298 | 299 | function tabQueue:hasTaskByFilter(filter) 300 | for index in ipairs(self._queue) do 301 | local tab = self._queue[index].tab 302 | if filter(tab) then 303 | return true 304 | end 305 | end 306 | return false 307 | end 308 | 309 | function tabQueue:openQueue() 310 | if self.isOpen then 311 | return 312 | end 313 | self.isOpen = true 314 | if #self:getCurTasks() <= 0 then 315 | self:startNextTask() 316 | end 317 | end 318 | 319 | function tabQueue:closeQueue() 320 | self.isOpen = false 321 | end 322 | 323 | function tabQueue:_addToQueueByPriority(data) 324 | local index = 1 325 | while index <= #self._queue do 326 | local old = self._queue[index] 327 | if data.priority > old.priority then 328 | index = index + 1 329 | else 330 | break 331 | end 332 | end 333 | 334 | table.insert(self._queue, index, data) 335 | end 336 | 337 | function tabQueue:hasTypeInTop(type) 338 | local hasType = false 339 | self:forEachSub(function(sub) 340 | if sub.tabType == type then 341 | hasType = true 342 | return true 343 | end 344 | end) 345 | return hasType 346 | end 347 | 348 | function tabQueue:getAllTaskCount() 349 | -- 队列数 350 | local queueCount = table.nums(self._queue) 351 | -- 当前运行数 352 | queueCount = queueCount + #self:getCurTasks() 353 | return queueCount 354 | end 355 | 356 | function tabQueue:getTaskCount(tabType) 357 | local count = 0 358 | for k, data in pairs(self._queue) do 359 | if data.tab.tabType == tabType then 360 | count = count + 1 361 | end 362 | end 363 | return count 364 | end 365 | 366 | function tabQueue:isTaskRuning() 367 | if #self:getCurTasks() <= 0 then 368 | return false 369 | end 370 | return true 371 | end 372 | 373 | function tabQueue:existUid(uid) 374 | if uid == 0 then 375 | return false 376 | end 377 | for _, data in pairs(self._queue) do 378 | if data.uid == uid then 379 | return true 380 | end 381 | end 382 | if self._curTabConfigMap[uid] then 383 | return true 384 | end 385 | return false 386 | end 387 | 388 | function tabQueue:removeByUid(uid) 389 | -- 如果当前运行的就是目标对象,则直接停止当前对象 390 | if self._curTabConfigMap[uid] then 391 | local tab = self._curAliveMap:get(uid) 392 | if tab then 393 | tab:stop() 394 | end 395 | return 396 | end 397 | -- 移除队列里面的对象 398 | self:_("removeByUid", uid) 399 | end 400 | 401 | function tabQueue:waitTabCompleted(uid, dontStopHostWhenStop) 402 | -- 如果要等待的是当前的tab,那么直接返回当前的proxy 403 | if self._curTabConfigMap[uid] then 404 | local tab = self._curAliveMap:get(uid) 405 | if tab then 406 | return tab:tabProxy(nil, not dontStopHostWhenStop) 407 | else 408 | return nil 409 | end 410 | end 411 | if not self:existUid(uid) then 412 | return nil 413 | end 414 | 415 | local waitTabMgr = self:getSub("waitTabCompletedMgr") 416 | if not waitTabMgr then 417 | waitTabMgr = self:call(tabQueue._tabWaitTabCompletedMgr(uid), "waitTabCompletedMgr") 418 | else 419 | waitTabMgr:addTab(uid) 420 | end 421 | return waitTabMgr:getWatiTabProxy(uid, not dontStopHostWhenStop) 422 | end 423 | 424 | tabQueue._tabWaitTabCompletedMgr = _{ 425 | s1 = function(c, uid) 426 | c.aliveMap = c:call(g_t.tabAliveMap, "aliveMap") 427 | c.refCount = c:call(g_t.tabRefCount, "refCount") 428 | c:addTab(uid) 429 | end, 430 | 431 | refCount_event = { 432 | zero = function(c) 433 | c:stop() 434 | end, 435 | }, 436 | 437 | event = { 438 | runningTab = function(c, uid, context) 439 | local waitTab = c.aliveMap:get(uid) 440 | if waitTab then 441 | waitTab:notify("running", context) 442 | end 443 | c.aliveMap:validate() 444 | end, 445 | 446 | removeTab = function(c, uid) 447 | c:removeTab(uid) 448 | end, 449 | }, 450 | 451 | -- public: 452 | 453 | addTab = function(c, uid) 454 | -- 重复添加直接返回 455 | local waitTab = c.aliveMap:get(uid) 456 | if waitTab then 457 | return 458 | end 459 | local tab = c:call(tabQueue._tabMonitor(uid), uid) 460 | c.aliveMap:set(uid, tab) 461 | c.refCount:acquire(tab, "monitor") 462 | end, 463 | 464 | removeTab = function(c, uid) 465 | local waitTab = c.aliveMap:get(uid) 466 | if waitTab then 467 | waitTab:sendRemoveMsg() 468 | waitTab:stop() 469 | end 470 | c.aliveMap:validate() 471 | end, 472 | 473 | getWatiTabProxy = function(c, uid, stopHostWhenStop) 474 | local waitTab = c.aliveMap:get(uid) 475 | if waitTab then 476 | return waitTab:tabProxy(nil, stopHostWhenStop) 477 | end 478 | c.aliveMap:validate() 479 | return nil 480 | end, 481 | } 482 | 483 | tabQueue._tabMonitor = _{ 484 | s1 = function(c, uid) 485 | c._uid = uid 486 | c._nickName = "monitor_".. c._uid 487 | c.running = false 488 | end, 489 | 490 | s1_event = { 491 | running = function(c, tab) 492 | c.running = true 493 | c.tab = tab 494 | c:stop("s1") 495 | end, 496 | }, 497 | 498 | s2 = function(c) 499 | c._nickName = "running_".. c._uid 500 | c:call(c.tab:tabProxy(nil, true),"s3") 501 | end, 502 | 503 | s4 = function(c) 504 | local outputs = c.tab:getOutputs() 505 | if outputs ~= nil then 506 | c:output(table.unpack(outputs)) 507 | end 508 | end, 509 | 510 | final = function(c) 511 | if c.running then return end 512 | -- 从列表中删除排队对象 513 | c:_("removeByUid", c._uid) 514 | end, 515 | 516 | sendRemoveMsg = function(c) 517 | c:upwardNotify("tabQueue_remove") 518 | end, 519 | 520 | --public: 521 | 522 | uid = function(c) 523 | return c._uid 524 | end, 525 | 526 | } 527 | 528 | return tabQueue 529 | -------------------------------------------------------------------------------- /tabAction.lua: -------------------------------------------------------------------------------- 1 | --author lch 2 | 3 | -------------------------------actions ------------------- 4 | g_t.waitAnimatorStatusChange = _{ 5 | s1 = function(c, animator, statusName) 6 | c._nickName = "waitAnimatorStatusChange" 7 | c.animator = animator 8 | c.statusName = statusName 9 | c.normalizedTimeList = {} 10 | end, 11 | s1_update = function(c) 12 | local nextAnimatorStatusInfo = c.animator:GetNextAnimatorStateInfo(0) 13 | if not nextAnimatorStatusInfo:IsName(c.statusName) then 14 | c:stop("s1") 15 | end 16 | end, 17 | s2 = g_t.empty_fun, 18 | s2_update = function(c) 19 | local animatorStatusInfo = c.animator:GetCurrentAnimatorStateInfo(0) 20 | if not animatorStatusInfo:IsName(c.statusName) then 21 | c:stop() 22 | end 23 | if animatorStatusInfo.normalizedTime >= 1 then 24 | c:stop() 25 | end 26 | for _,normalizedTime in ipairs(c.normalizedTimeList) do 27 | if animatorStatusInfo.normalizedTime > normalizedTime then 28 | c:stop(normalizedTime + "normalizedTime") 29 | end 30 | end 31 | end, 32 | 33 | tabProxyForNormalizedTime = function(c, normalizedTime) 34 | if not c:getSub(normalizedTime + "normalizedTime") then 35 | table.insert(c.normalizedTimeList, normalizedTime) 36 | c:call(_{ 37 | s1 = g_t.empty_fun, 38 | event = g_t.empty_event, 39 | }, normalizedTime + "normalizedTime") 40 | end 41 | return c:tabProxy(normalizedTime + "normalizedTime") 42 | end, 43 | 44 | --public 45 | tabProxyKeyFrame = function (c, keyFrame) 46 | if not c.frameEvent then 47 | c.frameEvent = true 48 | end 49 | if not c:getSub(keyFrame) then 50 | c:call(c:tabKeyFrame(keyFrame), keyFrame) 51 | end 52 | return c:tabProxy(keyFrame) 53 | end, 54 | -- private: 55 | tabKeyFrame = function(c) 56 | return _{ 57 | s1 = g_t.empty_fun, 58 | event = g_t.empty_event, 59 | } 60 | end, 61 | 62 | keyFrameEventCall = function(c, keyFrame) 63 | c:stop(keyFrame) 64 | end, 65 | } 66 | 67 | g_t.waitAniChangeByFrame = _{ 68 | s1 = function(c, animator, clipName) 69 | c._nickName = "waitAnimatorStatusChange" 70 | c.animator = animator 71 | c.clipName = clipName 72 | c.normalizedTimeList = {} 73 | c.animator:AddLastFrameEvent(c.clipName) 74 | end, 75 | 76 | event = g_t.empty_event, 77 | 78 | --public 79 | tabProxyKeyFrame = function (c, keyFrame, frameTime) 80 | if not c:getSub(keyFrame) then 81 | if frameTime then 82 | c.animator.AddFrameEvent(c.clipName, keyFrame, frameTime) 83 | end 84 | c:call(c:tabKeyFrame(keyFrame), keyFrame) 85 | end 86 | return c:tabProxy(keyFrame) 87 | end, 88 | 89 | -- private: 90 | tabKeyFrame = function(c) 91 | return _{ 92 | s1 = g_t.empty_fun, 93 | event = g_t.empty_event, 94 | } 95 | end, 96 | 97 | keyFrameEventCall = function(c, keyFrame) 98 | if keyFrame == "lastFrame" then 99 | c:stop() 100 | else 101 | c:stop(keyFrame) 102 | end 103 | end, 104 | } 105 | g_t.waitForNormalizedTime = _{ 106 | s1 = function(c, animator, statusName, normalizedTime) 107 | c.normalizedTime = normalizedTime 108 | c.animator = animator 109 | c.statusName = statusName 110 | end, 111 | s1_update = function(c) 112 | local animatorStatusInfo = c.animator:GetCurrentAnimatorStateInfo(0) 113 | if animatorStatusInfo:IsName(c.statusName) then 114 | c:stop("s1") 115 | end 116 | end, 117 | s2 = g_t.empty_fun, 118 | s2_update = function(c) 119 | local animatorStatusInfo = c.animator:GetCurrentAnimatorStateInfo(0) 120 | if not animatorStatusInfo:IsName(c.statusName) or animatorStatusInfo.normalizedTime >= c.normalizedTime then 121 | c:stop("s2") 122 | end 123 | end, 124 | } 125 | 126 | g_t.waitForLastFrame = _{ 127 | s1 = function(c, animation) 128 | c._nickName = "waitForLastFrame" 129 | c.animation = animation 130 | end, 131 | 132 | s1_update = function(c) 133 | if not c.animation.isPlaying then 134 | c:stop() 135 | end 136 | end, 137 | 138 | --public 139 | tabProxyKeyFrame = function (c, keyFrame) 140 | local sub = c:getSub(keyFrame) 141 | if not sub then 142 | c:call(c:tabKeyFrame(keyFrame), keyFrame) 143 | sub = c:getSub(keyFrame) 144 | end 145 | return sub:tabProxy() 146 | end, 147 | -- private: 148 | tabKeyFrame = function(c, keyFrame) 149 | return _{ 150 | s1 = g_t.empty_fun, 151 | event = { 152 | keyFrame = function(c, frame) 153 | if keyFrame == frame then 154 | c:stop() 155 | end 156 | end 157 | }, 158 | } 159 | end, 160 | 161 | keyFrameEventCall = function(c, keyFrame) 162 | c:notify("keyFrame", keyFrame) 163 | end, 164 | 165 | event = g_t.empty_event, 166 | } 167 | 168 | g_t.waitForAct = _{ 169 | s1 = function (c, node, act) 170 | if g_t.debug then 171 | c._nickName = "waitForAct" 172 | end 173 | transition.execute(node, act, {onComplete = function() 174 | c:stop() 175 | end}) 176 | if act.getDuration then 177 | c:call(g_t.delay, "s2", nil, act:getDuration()) 178 | end 179 | end, 180 | s3 = function(c) 181 | c:stop() 182 | end, 183 | event = g_t.empty_event, 184 | } 185 | 186 | g_t.playSpineAnimation = _{ 187 | s1 = function(c, spine, animationName) 188 | c.spine = spine 189 | spine:registerSpineEventHandler(function (data) 190 | if data.type == spEventTypeString.SP_ANIMATION_COMPLETE 191 | and animationName == data.animation then 192 | c:stop() 193 | end 194 | end,spEventType.SP_ANIMATION_COMPLETE) 195 | spine:setAnimation(0, animationName, false) 196 | end, 197 | 198 | final = function(c) 199 | local spine = c.spine 200 | if not tolua.isnull(spine) then 201 | spine:unregisterSpineEventHandler(sp.EventType.ANIMATION_COMPLETE) 202 | end 203 | end, 204 | event = g_t.empty_event, 205 | } 206 | local math_pow = math.pow or function(x, y) return x^y end 207 | 208 | g_t.curve = {} 209 | g_t.curve.circleEaseOut = function(k) 210 | local value = k - 1 211 | return math.sqrt(1 - value * value) 212 | end 213 | 214 | g_t.curve.circleEaseIn = function(k) 215 | local value = k 216 | return -1 * (math.sqrt(1 - value * value) - 1) 217 | end 218 | 219 | g_t.curve.circleEaseInOut = function(k) 220 | local value = k*2 221 | if value < 1 then 222 | return -0.5*(math.sqrt(1 - value * value) - 1) 223 | end 224 | value = value - 2 225 | return 0.5 * (math.sqrt(1 - value * value) + 1) 226 | end 227 | 228 | g_t.curve.quadEaseOut = function(k) 229 | local value = k 230 | return -1 * value * (value - 2) 231 | end 232 | 233 | g_t.curve.quadEaseIn = function(k) 234 | local value = k 235 | return value * value 236 | end 237 | 238 | g_t.curve.quadEaseInOut = function(k) 239 | local value = k * 2 240 | if value < 1 then 241 | return 0.5 * value * value 242 | end 243 | value = value - 1 244 | return -0.5*(value*(value-2)-1) 245 | end 246 | 247 | g_t.curve.cubicEaseIn = function(k) 248 | local value = k 249 | return k * value * value 250 | end 251 | 252 | g_t.curve.cubicEaseOut = function (k) 253 | local value = k - 1 254 | return value*value*value+1 255 | end 256 | 257 | g_t.curve.cubicEaseInOut = function(k) 258 | local value = k * 2 259 | if value < 1 then 260 | return 0.5 * value * value * value 261 | end 262 | value = value - 2 263 | return 0.5 * (value * value * value + 2) 264 | end 265 | 266 | g_t.curve.sineEaseIn = function (k) 267 | return -1*math.cos(k*math.pi/2) + 1 268 | end 269 | 270 | g_t.curve.sineEaseOut = function (k) 271 | return math.sin(k*math.pi/2) 272 | end 273 | 274 | g_t.curve.sineEaseInOut = function (k) 275 | return -0.5*(math.cos(math.pi*k)-1) 276 | end 277 | 278 | g_t.curve.expoEaseIn = function (k) 279 | return math_pow(2, 10*(k-1)) 280 | end 281 | 282 | g_t.curve.expoEaseOut = function (k) 283 | return math.sin(k*math.pi/2) 284 | end 285 | 286 | g_t.curve.expoEaseInOut = function (k) 287 | return -0.5*(math.cos(math.pi*k)-1) 288 | end 289 | 290 | g_t.curve.quintEaseIn = function (k) 291 | return k * k * k * k * k 292 | end 293 | 294 | g_t.curve.quintEaseOut = function (k) 295 | k = k - 1 296 | return k * k * k * k * k + 1 297 | end 298 | 299 | g_t.curve.quintEaseInOut = function (k) 300 | k = k * 2 301 | if k < 1 then 302 | return 0.5 * k * k * k * k * k 303 | end 304 | k= k - 2 305 | return 0.5 * (k * k * k * k * k + 2) 306 | end 307 | 308 | g_t.curve.easeInQuart = function(k) 309 | return k * k * k * k 310 | end 311 | 312 | g_t.curve.easeOutQuart = function(k) 313 | return 1 - math_pow(1 - k, 4) 314 | end 315 | 316 | g_t.curve.easeInOutQuart = function(k) 317 | if k < 0.5 then 318 | return 8 * k * k * k * k 319 | end 320 | return 1 - (-2 * k + 2)^ 4 / 2 321 | end 322 | 323 | g_t.curve.easeInBack = function(k) 324 | local k1 = 1.70158 325 | local k2 = k1 + 1 326 | return k2 * k * k * k - k1 * k * k 327 | end 328 | 329 | g_t.curve.easeOutBack = function(k) 330 | local k1 = 1.70158 331 | local k2 = k1 + 1 332 | return 1 + k2 * math_pow(k - 1, 3) + k1 * math_pow(k - 1, 2) 333 | end 334 | 335 | g_t.curve.easeInOutBack = function(k) 336 | local k1 = 1.70158 337 | local k2 = k1 * 1.525; 338 | 339 | k = k * 2 340 | if k < 1 then 341 | return 0.5 * (k * k * ((k2 + 1) * k - k2)) 342 | end 343 | k = k - 2 344 | return 0.5 * (k * k * ((k2 + 1) * k + k2) + 2) 345 | end 346 | 347 | g_t.curve.easeInElastic = function(k) 348 | local k1 = 2 / 3 * math.pi 349 | 350 | if k == 0 then 351 | return 0 352 | elseif k == 1 then 353 | return 1 354 | end 355 | return -1 * math_pow(2, 10 * k - 10) * math.sin((k * 10 - 10.75) * k1) 356 | end 357 | 358 | g_t.curve.easeOutElastic = function(k) 359 | local k1 = 2 / 3 * math.pi 360 | if k == 0 then 361 | return 0 362 | elseif k == 1 then 363 | return 1 364 | end 365 | return math_pow(2, -10 * k) * math.sin((k * 10 - 0.75) * k1) + 1 366 | end 367 | 368 | g_t.curve.easeInOutElastic = function(k) 369 | local k1 = 4 / 9 * math.pi 370 | if k == 0 then 371 | return 0 372 | elseif k == 1 then 373 | return 1 374 | end 375 | k = k * 20 376 | if k < 10 then 377 | return - 0.5 * (math_pow(2, k - 10) * math.sin((k - 11.125) * k1)) 378 | end 379 | return 0.5 * math_pow(2, -k + 10) * math.sin((k - 11.125) * k1)+ 1 380 | end 381 | 382 | g_t.curve.easeOutBounce = function(k) 383 | local k1 = 7.5625; 384 | local k2 = 2.75; 385 | if (k < 1 / k2) then 386 | return k1 * k * k; 387 | elseif (k < 2 / k2) then 388 | local k = k - 1.5/k2 389 | return k1 * k * k + 0.75 390 | elseif (k < 2.5 / k2) then 391 | local k2 = k - 2.25/k2 392 | return k1 * k2 * k2 + 0.9375 393 | else 394 | local k3 = k - 2.625/k2 395 | return k1 * k3 * k3 + 0.984375 396 | end 397 | end 398 | 399 | g_t.curve.easeInBounce = function(k) 400 | return 1 - g_t.curve.easeOutBounce(1-k) 401 | end 402 | 403 | g_t.curve.easeInOutBounce = function(k) 404 | k = k * 2 405 | if k < 1 then 406 | return 0.5 - 0.5 * g_t.curve.easeOutBounce(1 - k) 407 | else 408 | return 0.5 + 0.5 * g_t.curve.easeOutBounce(k - 1) 409 | end 410 | end 411 | 412 | g_t.curve.defaultLine = function (k) 413 | return k 414 | end 415 | 416 | g_t.tween = _{ 417 | s1 = function(c, fun, v1, v2, duration, curve) 418 | if g_t.debug then 419 | c._nickName = "tween" 420 | end 421 | fun(v1) 422 | c.time = 0 423 | c.duration = duration 424 | c.v1 = v1 425 | c.v2 = v2 426 | c.fun = fun 427 | c.curve = curve 428 | end, 429 | 430 | s1_update = function(c, dt) 431 | c.time = c.time + dt 432 | if c.time > c.duration then 433 | c:stop("s1") 434 | return 435 | end 436 | 437 | local rate = c.time / c.duration 438 | local v 439 | if c.curve and type(c.curve) == "function" then 440 | local tempRate = c.curve(rate) 441 | v = (c.v2 - c.v1)*tempRate + c.v1 442 | else 443 | v = c.v1 * (1.0 - rate) + c.v2 * rate 444 | end 445 | c.fun(v) 446 | end, 447 | 448 | s2 = function(c) 449 | c.fun(c.v2) 450 | end, 451 | 452 | _preCal = function(v1, v2, curDuration, duration, curve) 453 | local rate = curDuration / duration 454 | local v 455 | if curve and type(curve) == "function" then 456 | local tempRate = curve(rate) 457 | v = (v2 - v1)*tempRate + v1 458 | else 459 | v = v1 * (1.0 - rate) + v2 * rate 460 | end 461 | return v 462 | end 463 | } 464 | 465 | --二阶 466 | g_t.curve.bezierOnePoint = function(t, p0, p1, p2) 467 | local p0p1 = mathLib.lerpVec3(p0, p1, t) 468 | local p1p2 = mathLib.lerpVec3(p1, p2, t) 469 | local result = mathLib.lerpVec3(p0p1, p1p2, t) 470 | return result 471 | end 472 | 473 | --三阶 474 | g_t.curve.bezierTwoPoint = function(t, p0, p1, p2, p3) 475 | local p0_1 = mathLib.lerpVec3(p0,p1,t) 476 | local p1_2 = mathLib.lerpVec3(p1,p2,t) 477 | local p2_3 = mathLib.lerpVec3(p2,p3,t) 478 | local p0_1_1_2 = mathLib.lerpVec3(p0_1,p1_2,t) 479 | local p1_2_2_3 = mathLib.lerpVec3(p1_2,p2_3,t) 480 | local p0_1_1_2_1_2_2_3 = mathLib.lerpVec3(p0_1_1_2,p1_2_2_3,t) 481 | return p0_1_1_2_1_2_2_3 482 | end 483 | 484 | g_t.bezier = _{ 485 | s1 = function(c, fun, v1, v2, duration, curve, points) 486 | if g_t.debug then 487 | c._nickName = "bezier" 488 | end 489 | fun(v1) 490 | c.time = 0 491 | c.duration = duration 492 | c.v1 = v1 493 | c.v2 = v2 494 | c.fun = fun 495 | c.curve = curve 496 | c.points = points 497 | c.rate = {} 498 | end, 499 | 500 | s1_update = function(c, dt) 501 | c.time = c.time + dt 502 | if c.time > c.duration then 503 | c:stop("s1") 504 | return 505 | end 506 | 507 | local rate = c.time / c.duration 508 | c.rate.x = rate 509 | c.rate.y = rate 510 | c.rate.z = rate 511 | local v 512 | if c.curve and type(c.curve) == "function" and type(c.points) == "table" then 513 | if (#c.points == 1) then 514 | v = c.curve(c.rate, c.v1, c.points[1], c.v2) 515 | elseif (#c.point == 2) then 516 | v = c.curve(c.rate, c.v1, c.points[1], c.points[2], c.v2) 517 | end 518 | else 519 | printError("bezier param is error") 520 | end 521 | c.fun(v) 522 | end, 523 | 524 | s2 = function(c) 525 | c.fun(c.v2) 526 | end, 527 | } 528 | 529 | g_t.printText = _{ 530 | s1 = function(c, labNode, word, interval) 531 | print("tabPrintText==========", word, interval) 532 | c.word = word or "" 533 | c.index = 0 534 | c.t = 0 535 | c.interval = interval or 0.1 536 | c.wordCount = str_util.subStringGetTotalIndex(word) 537 | c.labelNode = labNode 538 | if c.wordCount > 1 then 539 | local subWord = str_util.subStringUTF8(c.word, 1, 1) 540 | c.labelNode:setString(subWord) 541 | end 542 | end, 543 | s1_update = function(c, dt) 544 | c.index = c.index + 1 545 | if c.index <= c.wordCount then 546 | local subWord = str_util.subStringUTF8(c.word, 1, c.index) 547 | if not tolua.isnull(c.labelNode) then 548 | c.labelNode:setString(subWord) 549 | end 550 | else 551 | c:stop() 552 | end 553 | end, 554 | s1_updateInterval = interval, 555 | final = function(c) 556 | -- labNode = nil 557 | end 558 | } 559 | 560 | g_t.printTextEx = _{ 561 | s1 = function(c, labNode, word, interval, needUpwardNotify) 562 | labNode:setData(word) 563 | c.labCom = labNode:com(ct.text) 564 | c.curCount = 0 565 | c.labCom.maxVisibleCharacters = c.curCount 566 | c.needUpwardNotify = needUpwardNotify 567 | c:setDynamics("s3", "updateInterval", interval or 0.1) 568 | c:call(g_t.skipFrames, "s2", nil, 1) 569 | end, 570 | s3 = function(c) 571 | c.maxCount = c.labCom.textInfo.characterCount 572 | if (c.needUpwardNotify) then 573 | c:upwardNotify("onLabSizeFix") 574 | end 575 | end, 576 | s3_update = function(c) 577 | if c.curCount > c.maxCount then 578 | c:stop("s3") 579 | return 580 | end 581 | c.curCount = c.curCount + 1 582 | c.labCom.maxVisibleCharacters = c.curCount 583 | end, 584 | --overwrite in s1 setDynamics(s3) 585 | s3_updateInterval = nil, 586 | final = function(c) 587 | if (c.maxCount) then 588 | c.labCom.maxVisibleCharacters = c.maxCount 589 | end 590 | end 591 | } 592 | 593 | g_t.timeline_anim = _{ 594 | s1 = function(c, nodeList, timeLineConfig, anim) 595 | require("gameFlow.timeline.timeline_util") 596 | if g_t.debug then 597 | c._nickName = "timeline_anim" 598 | end 599 | local fps = timeLineConfig.fps 600 | local animConfigs = timeLineConfig[anim] 601 | for _, v in pairs(nodeList) do 602 | local name = v.name 603 | local go = v.go 604 | local animConfig = animConfigs[name] 605 | local loop = animConfigs.loop 606 | for key, attrCfg in pairs(animConfig) do 607 | c:call(c.playNode(key, attrCfg, go, loop, fps), "playNode" .. name .. key) 608 | end 609 | end 610 | end, 611 | 612 | playNode = _{ 613 | s1 = function(c, key, attrCfg, go, loop, fps) 614 | c.key = key 615 | c.object = go 616 | c.loop = loop 617 | c.frameDatas = timeline_util.parseFrameNodeConfig(attrCfg) 618 | c.totalCount = #c.frameDatas 619 | c.index = 1 620 | c.nextIndex = c.index + 1 621 | c.time = 0 622 | end, 623 | s2 = function(c) 624 | c.curFrame = c.frameDatas[c.index] 625 | c.nextFrame = c.frameDatas[c.nextIndex] 626 | c.beginTime = c.curFrame.time 627 | c.beginValue = c.curFrame.value 628 | c.endTime = c.nextFrame.time 629 | c.endValue = c.nextFrame.value 630 | c.curveType = c.curFrame.curve and g_t.curve[c.curFrame.curve] or g_t.curve.defaultLine 631 | end, 632 | s2_update = function(c, deltaTime) 633 | c.time = c.time + deltaTime 634 | local progress = math.min(1, (c.time - c.beginTime)/(c.endTime - c.beginTime)) 635 | progress = c.curveType(progress) 636 | local value = (c.endValue - c.beginValue) * progress + c.beginValue 637 | timeline_util.setObjectClipFrame(c.key, c.object, value) 638 | if c.time >= c.endTime then 639 | c:stop("s2") 640 | end 641 | end, 642 | s3 = function(c) 643 | c.index = c.index + 1 644 | c.nextIndex = c.index + 1 645 | if c.nextIndex <= c.totalCount then 646 | c:start("s2") 647 | elseif c.loop and c.loop == 1 then 648 | c.index = 1 649 | c.nextIndex = c.index + 1 650 | c.time = 0 651 | c:start("s2") 652 | else 653 | c:stop() 654 | end 655 | end, 656 | } 657 | } 658 | 659 | local getNodeAttribute 660 | local parseTimeLineData 661 | local setNodeAttribute 662 | ------------示例 663 | --eParams = {e1=function() ,e2 = function()} 664 | --g_t.timeline(node, "t=0|t=4,x=200,y=200,sx=0,a=1,r=90,e=e1|t=8,+x=300,+y=300,sx=1,a=255,r=0,e=e2", eParams) 665 | --timeStr参数说明t为时间节点,x,y,+x,+y为位置参数,s,sx,sy,+s,+sx,+sy为缩放参数,r,+r为角度旋转参数,d,+d为弧度旋转参数 666 | --a,+a为透明参数,e为回调函数或者tab 667 | 668 | -- {x y +x +y lx, ly +lx, +ly 未位置参数, s,sx,sy,+s,+sx,+sy} 669 | g_t.timeline = _{ 670 | s1 = function(c, gameObject, operateTable) 671 | if g_t.debug then 672 | c._nickName = "timeline" 673 | end 674 | c.time = 0 675 | c.gameObject = gameObject 676 | c.operateTable = operateTable 677 | c.lineData = parseTimeLineData(operateTable) 678 | end, 679 | s2 = function(c) 680 | c.curData = table.remove(c.lineData, 1) 681 | if c.curData then 682 | c:start("s4") 683 | end 684 | end, 685 | s4 = function(c) 686 | c.nodeAttribute = getNodeAttribute(c.gameObject, c.curData.animation) 687 | end, 688 | s4_update = function(c, dt) 689 | c.time = c.time + dt 690 | setNodeAttribute(c.gameObject, c.nodeAttribute, c.curData, c.operateTable, c.time) 691 | if c.time >= c.curData.animation.t then 692 | if c.curData.animation.f then 693 | if type(c.curData.animation.f) == "function" then 694 | c.curData.animation.f(c, c.gameObject) 695 | elseif type(c.curData.animation.f) == "table" then 696 | c:call(c.curData.animation.f(c.gameObject), "f") 697 | end 698 | end 699 | c:stop("s4") 700 | end 701 | end, 702 | s5 = function(c) 703 | c:start("s2") 704 | end, 705 | } 706 | 707 | parseTimeLineData = function(operateTable) 708 | local count = 1 709 | local list = {} 710 | local custom = {} 711 | local preValue = 0 712 | for index, v in ipairs(operateTable) do 713 | if not v.curve then 714 | list[count] = { 715 | animation = v, 716 | } 717 | custom = {} 718 | for type, value in pairs(v) do 719 | if operateTable[type] then 720 | preValue = 0 721 | for i = math.max(index - 1, 1), 1, -1 do 722 | if operateTable[i][type] then 723 | preValue = operateTable[i][type] 724 | break 725 | end 726 | end 727 | custom[type] = {preValue = preValue, value = value } 728 | end 729 | end 730 | list[count].custom = custom 731 | if operateTable[index + 1] and operateTable[index + 1].curve then 732 | list[count].curve = operateTable[index + 1].curve 733 | end 734 | count = count + 1 735 | end 736 | end 737 | for i, v in ipairs(list) do 738 | v.timeInterval = list[i - 1] and v.animation.t - list[i - 1].animation.t or v.animation.t 739 | end 740 | return list 741 | end 742 | 743 | getNodeAttribute = function (gameObject, curData, operateTable, custom) 744 | local data = {} 745 | if curData.x or curData["+x"] then 746 | data.x = CS.GameObjUtil.GetLocalPositionX(gameObject) 747 | end 748 | if curData.y or curData["+y"] then 749 | data.y = CS.GameObjUtil.GetLocalPositionY(gameObject) 750 | end 751 | if curData.s or curData["+s"] then 752 | data.s = CS.GameObjUtil.GetScale(gameObject) 753 | end 754 | if curData.a or curData["+a"] then 755 | local canvasGroup = gameObject:GetComponentInChildren("CanvasGroup") 756 | local alpha = 1 757 | if not isNil(canvasGroup) then 758 | alpha = canvasGroup.alpha 759 | end 760 | data.a = alpha 761 | end 762 | if curData.sx or curData["+sx"] then 763 | data.sx = CS.GameObjUtil.GetScaleX(gameObject) 764 | end 765 | if curData.sy or curData["+sy"] then 766 | data.sy = CS.GameObjUtil.GetScaleY(gameObject) 767 | end 768 | if curData.w then 769 | data.w, data.h = CS.GameObjUtil.GetSizeDelta(gameObject.rectTransform) 770 | end 771 | -- if curData.cr then 772 | -- local color = node:getColor() 773 | -- data.cr = color.r 774 | -- end 775 | -- if curData.cg then 776 | -- local color = node:getColor() 777 | -- data.cg = color.g 778 | -- end 779 | -- if curData.cb then 780 | -- local color = node:getColor() 781 | -- data.cb = color.b 782 | -- end 783 | return data 784 | end 785 | 786 | 787 | setNodeAttribute = function(gameObject, oldAttribute, operateData, operateTable, curTime) 788 | local rate = 1 789 | local finalAttribute = operateData.animation 790 | if finalAttribute.t > 0 then 791 | rate = (operateData.timeInterval - math.max(finalAttribute.t - curTime,0)) / operateData.timeInterval 792 | if operateData and operateData.curve then 793 | rate = operateData.curve(rate) 794 | end 795 | end 796 | if finalAttribute.x or finalAttribute["+x"] then 797 | local addX = finalAttribute.x and (finalAttribute.x - oldAttribute.x) or finalAttribute["+x"] 798 | local x = oldAttribute.x + addX * rate 799 | CS.GameObjUtil.SetLocalPositionX(gameObject, x) 800 | end 801 | if finalAttribute.y or finalAttribute["+y"] then 802 | local addY = finalAttribute.y and (finalAttribute.y - oldAttribute.y ) or finalAttribute["+y"] 803 | local y = oldAttribute.y + addY * rate 804 | CS.GameObjUtil.SetLocalPositionY(gameObject, y) 805 | end 806 | if finalAttribute.s or finalAttribute["+s"] then 807 | local addS = finalAttribute.s and (finalAttribute.s - oldAttribute.s ) or finalAttribute["+s"] 808 | local s = oldAttribute.s + addS * rate 809 | CS.GameObjUtil.SetScale(gameObject, s) 810 | end 811 | if finalAttribute.a or finalAttribute["+a"] then 812 | local addA = finalAttribute.a and (finalAttribute.a - oldAttribute.a ) or finalAttribute["+a"] 813 | local a = oldAttribute.a + addA * rate 814 | local canvasGroup = gameObject:GetComponentInChildren("CanvasGroup") 815 | if not isNil(canvasGroup) then 816 | canvasGroup.alpha = a 817 | end 818 | end 819 | if finalAttribute.sx or finalAttribute["+sx"] then 820 | local addSx = finalAttribute.sx and (finalAttribute.sx - oldAttribute.sx ) or finalAttribute["+sx"] 821 | local sx = oldAttribute.sx + addSx * rate 822 | CS.GameObjUtil.SetScaleX(gameObject, sx) 823 | end 824 | if finalAttribute.sy or finalAttribute["+sy"] then 825 | local addSy = finalAttribute.sy and (finalAttribute.sy - oldAttribute.sy ) or finalAttribute["+sy"] 826 | local sy = oldAttribute.sy + addSy * rate 827 | CS.GameObjUtil.SetScaleY(gameObject, sy) 828 | end 829 | if finalAttribute.w then 830 | local addW = finalAttribute.w and (finalAttribute.w - oldAttribute.w ) 831 | local sw = oldAttribute.w + addW * rate 832 | CS.GameObjUtil.SetSizeDelta(gameObject.rectTransform, sw, oldAttribute.h) 833 | end 834 | for type, customValue in pairs(operateData.custom) do 835 | local addCustom = customValue.value - customValue.preValue 836 | local addValue = customValue.preValue + addCustom * rate 837 | operateTable[type](addValue) 838 | end 839 | end 840 | 841 | local function splitStr(inputstr, sep) 842 | local t={} ; i=1 843 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do 844 | t[i] = str 845 | i = i + 1 846 | end 847 | return t 848 | end 849 | 850 | -- 由于数组起始索引lua和c++有所不同,代码有做适当改动 851 | -- node: 852 | -- 1.为node时自动改变节点位置 853 | -- 2.为nil时仅作为纯计算用途 854 | function g_t.catmullRom(node, duration, points, needAutoRotation) 855 | assert(type(points) == "table", "points must be array") 856 | assert(#points > 1, "point num must greater than 1") 857 | assert(duration > 0, "duration must greater than zero!") 858 | return _{ 859 | s1 = function(c) 860 | if g_t.debug then 861 | c._nickName = "catmullRom" 862 | end 863 | c.deltaT = 1 / (#points - 1) 864 | c.elapsed = 0 865 | c.dir = cc.p(0, 0) 866 | if node and type(node) == "userdata" then 867 | local x, y = node:getPosition() 868 | c.pos = cc.p(x, y) 869 | else 870 | c.pos = points[1] 871 | end 872 | end, 873 | 874 | update = function(c, deltaTime) 875 | local p; 876 | local lt; 877 | c.elapsed = c.elapsed + deltaTime 878 | local time = c.elapsed / (duration > 0 and duration or 1.4e-45) 879 | local reached = false 880 | if time >= 1 then 881 | reached = true 882 | time = 1 883 | end 884 | if (time == 1) then 885 | p = #points 886 | lt = 1 887 | else 888 | p = math.floor(time / c.deltaT) 889 | lt = (time - c.deltaT * p) / c.deltaT 890 | p = p + 1 891 | end 892 | 893 | -- Interpolate 894 | local pp0 = c:_getControlPointAtIndex(p - 1) 895 | local pp1 = c:_getControlPointAtIndex(p + 0) 896 | local pp2 = c:_getControlPointAtIndex(p + 1) 897 | local pp3 = c:_getControlPointAtIndex(p + 2) 898 | local newPos = c:_cardinalSplineAt(pp0, pp1, pp2, pp3, lt) 899 | c.pos = newPos 900 | if node and not tolua.isnull(node) then 901 | node:setPosition(newPos) 902 | end 903 | if time ~= 1 then 904 | -- 获取方向 905 | if needAutoRotation then 906 | local dir = c:_dirAt(pp0, pp1, pp2, pp3, lt) 907 | c.dir = dir 908 | if node and not tolua.isnull(node) then 909 | local angle = math.radian2angle(math.atan2(dir.y, dir.x)) 910 | node:setRotation(-angle) 911 | end 912 | end 913 | end 914 | if reached then 915 | c:stop() 916 | end 917 | end, 918 | 919 | getCurPos = function(c) 920 | return c.pos 921 | end, 922 | 923 | getDir = function (c) 924 | return c.dir 925 | end, 926 | 927 | _cardinalSplineAt = function (c, p0, p1, p2, p3, t) 928 | local t2 = t * t 929 | local t3 = t2 * t 930 | -- 931 | -- Formula: s(-ttt + 2tt - t)P1 + s(-ttt + tt)P2 + (2ttt - 3tt + 1)P2 + s(ttt - 2tt + t)P3 + (-2ttt + 3tt)P3 + s(ttt - tt)P4 932 | -- 933 | local s = (1 - 0.5) / 2 934 | 935 | local b1 = s * ((-t3 + (2 * t2)) - t) -- s(-t3 + 2 t2 - t)P1 936 | local b2 = s * (-t3 + t2) + (2 * t3 - 3 * t2 + 1) -- s(-t3 + t2)P2 + (2 t3 - 3 t2 + 1)P2 937 | local b3 = s * (t3 - 2 * t2 + t) + (-2 * t3 + 3 * t2) -- s(t3 - 2 t2 + t)P3 + (-2 t3 + 3 t2)P3 938 | local b4 = s * (t3 - t2) -- s(t3 - t2)P4 939 | 940 | local x = (p0.x * b1 + p1.x * b2 + p2.x * b3 + p3.x * b4) 941 | local y = (p0.y * b1 + p1.y * b2 + p2.y * b3 + p3.y * b4) 942 | return cc.p(x,y) 943 | end, 944 | 945 | _dirAt = function (c, p0, p1, p2, p3, t) 946 | local t2 = t * t 947 | local s = (1 - 0.5) / 2 948 | local b1 = s * (-3 * t2 + 4 * t - 1) 949 | local b2 = s * (-3 * t2 + 2 * t) + (6 * t2 - 6 * t) 950 | local b3 = s * (3 * t2 - 4 * t + 1) + (-6 * t2 + 6 * t) 951 | local b4 = s * (3 * t2 - 2 * t) 952 | local x = (p0.x * b1 + p1.x * b2 + p2.x * b3 + p3.x * b4) 953 | local y = (p0.y * b1 + p1.y * b2 + p2.y * b3 + p3.y * b4) 954 | return cc.p(x, y) 955 | end, 956 | 957 | _getControlPointAtIndex = function (c, index) 958 | return points[util.clampf(index, 1, #points)] 959 | end, 960 | 961 | } 962 | end 963 | 964 | return cocosContext 965 | -------------------------------------------------------------------------------- /cocosContext.lua: -------------------------------------------------------------------------------- 1 | --author cs 2 | --email 04nycs@gmail.com 3 | --https://github.com/ThinEureka/tabMachine 4 | --created on July 11, 2019 5 | 6 | 7 | local tabMachine = require("tabMachine.tabMachine") 8 | local socket = require("socket") 9 | 10 | local cocosContext = tabMachine.context 11 | cocosContext.isTabClass = true 12 | local context_stopSelf = cocosContext._stopSelf 13 | -- cocosContext.reuse = true 14 | 15 | --inline optimization 16 | -- cocosContext.p_ctor = cocosContext.ctor 17 | 18 | -- function cocosContext:ctor() 19 | -- cocosContext.p_ctor(self) 20 | -- end 21 | 22 | 23 | local tabMachine_pcall = tabMachine._pcall 24 | 25 | function cocosContext:registerMsg(msg, fun, selfObj) 26 | -- if self.__lifeState >= lifeState.quitting then 27 | if self.__lifeState >= 20 then 28 | return 29 | end 30 | 31 | self.__needDispose = true 32 | self._hasMsg = true 33 | 34 | if selfObj then 35 | g_msgMgr:addMsg(self, msg, function(...) 36 | tabMachine_pcall(self, fun, ...) 37 | end, selfObj) 38 | else 39 | g_msgMgr:addMsg(self, msg, function(_, ...) 40 | tabMachine_pcall(self, fun, ...) 41 | end) 42 | end 43 | end 44 | 45 | function cocosContext:registerMsgEx(msgTable) 46 | for k, v in pairs(msgTable) do 47 | if type(k) == "string" then 48 | self:registerMsg(g_msg[k], v, self) 49 | elseif type(k) == "table" then 50 | for _, msg in ipairs(k) do 51 | self:registerMsg(g_msg[msg], v, self) 52 | end 53 | end 54 | end 55 | end 56 | 57 | function cocosContext:unRegisterMsg(msg) 58 | if self._hasMsg then 59 | g_msgMgr:removeMsgByName(self, msg) 60 | end 61 | end 62 | 63 | function cocosContext:registerMsgs(msgs, fun) 64 | -- if self.__lifeState >= lifeState.quitting then 65 | if self.__lifeState >= 20 then 66 | return 67 | end 68 | 69 | for _, msg in ipairs(msgs) do 70 | self:registerMsg(msg, fun) 71 | end 72 | end 73 | 74 | function cocosContext:registerButtonClick(target, fun, monitor) 75 | -- if self.__lifeState >= lifeState.quitting then 76 | if self.__lifeState >= 20 then 77 | return 78 | end 79 | 80 | if self.btnClickList == nil then 81 | self.btnClickList = {} 82 | end 83 | self.__needDispose = true 84 | local button 85 | if type(target) == "table" then 86 | button = target:com(ct.button) 87 | else 88 | button = target 89 | end 90 | 91 | if self.btnClickList[button] then 92 | return 93 | end 94 | 95 | local pc = self.__pc 96 | if pc == nil then 97 | pc = self 98 | end 99 | 100 | local monitorRef = nil 101 | if monitor ~= nil then 102 | monitorRef = g_t.aliveRef(monitor) 103 | end 104 | -- local pcName = self._pcName 105 | -- local pcAction = self._action 106 | local action = CS.Utils.AddButtonListener(button, function (...) 107 | -- pc.__pc = pc 108 | -- pc.__pcName = pcName 109 | -- pc.__pcAction = pcAction 110 | 111 | local monitor = nil 112 | if monitorRef then 113 | monitor = monitorRef:getTarget() 114 | end 115 | 116 | if monitor == nil or monitor:isIdle() then 117 | tabMachine_pcall(self, fun, ...) 118 | end 119 | 120 | end) 121 | self.btnClickList[button] = action 122 | end 123 | 124 | function cocosContext:unregisterAllButtonClick(target) 125 | local button 126 | if type(target) == "table" then 127 | button = target:com(ct.button) 128 | else 129 | button = target 130 | end 131 | 132 | button.onClick:RemoveAllListeners() 133 | if self.btnClickList and self.btnClickList[button] then 134 | self.btnClickList[button] = nil 135 | end 136 | end 137 | 138 | 139 | function cocosContext:retainAsyncOperationHandle(handle, releaseFunc) 140 | -- if self.__lifeState >= lifeState.quitting then 141 | if self.__lifeState >= 20 then 142 | return 143 | end 144 | 145 | self.__needDispose = true 146 | if not handle then 147 | printError("retainAsyncOperationHandle handle is nil") 148 | return 149 | end 150 | if not self.asyncOperationHandlePool then 151 | self.asyncOperationHandlePool = {} 152 | end 153 | table.insert(self.asyncOperationHandlePool, {handle = handle, releaseFunc = releaseFunc}) 154 | end 155 | 156 | function cocosContext:releaseAsyncOperationHandle(handle) 157 | if not self.asyncOperationHandlePool then 158 | return 159 | end 160 | 161 | local index = nil 162 | for i = #self.asyncOperationHandlePool, 1, -1 do 163 | local v = self.asyncOperationHandlePool[i] 164 | if handle==v.handle and v.releaseFunc then 165 | v.releaseFunc(v.handle) 166 | index = k 167 | break 168 | end 169 | 170 | end 171 | 172 | if index then 173 | table.remove(self.asyncOperationHandlePool, index) 174 | end 175 | 176 | end 177 | function cocosContext:releaseAllAsyncOperationHandle() 178 | if not self.asyncOperationHandlePool then 179 | return 180 | end 181 | 182 | for k, v in pairs(self.asyncOperationHandlePool) do 183 | if v.releaseFunc then 184 | tabMachine_pcall(self, v.releaseFunc, v.handle) 185 | end 186 | end 187 | self.asyncOperationHandlePool = nil 188 | end 189 | 190 | local removeBtnListener = function(target, action) 191 | target.onClick:RemoveListener(action) 192 | end 193 | 194 | function cocosContext:dispose() 195 | if self._hasMsg then 196 | g_msgMgr:removeMsgByTarget(self) 197 | end 198 | 199 | if self.btnClickList then 200 | for target, action in pairs(self.btnClickList) do 201 | tabMachine_pcall(self, removeBtnListener, target, action) 202 | end 203 | self.btnClickList = nil 204 | end 205 | 206 | if self.asyncOperationHandlePool then 207 | self:releaseAllAsyncOperationHandle() 208 | end 209 | end 210 | 211 | function cocosContext:getObject(path) 212 | if path == "" then 213 | return self 214 | end 215 | 216 | local object = self 217 | local beginIndex = 1 218 | 219 | local len = path:len() 220 | while object ~= nil and beginIndex <= len do 221 | local key = nil 222 | 223 | local endIndex = path:find(".", beginIndex, true) 224 | if endIndex == nil then 225 | endIndex = len + 1 226 | end 227 | 228 | key = path:sub(beginIndex, endIndex - 1) 229 | 230 | if key == "^" then 231 | object = self.p 232 | else 233 | object = object:getSub(key) 234 | end 235 | 236 | beginIndex = endIndex + 1 237 | end 238 | 239 | return object 240 | end 241 | 242 | function cocosContext:getTreeMsg() 243 | local msg = self:getMsg(self) 244 | return msg 245 | end 246 | 247 | function cocosContext:getMsg(context, prefix, msg) 248 | local xx = prefix or "" 249 | local name = xx..context.__name 250 | if context.tabName and context.tabName ~= "cocosContext" then 251 | name = name .. "(" .. context.tabName .. ")" 252 | end 253 | 254 | if context._nickName then 255 | name = name .. "[" .. context._nickName .. "]" 256 | end 257 | 258 | msg = msg or "" 259 | msg = msg .. "\n" .. name 260 | print(name) 261 | 262 | local subContext = context._headSubContext 263 | while subContext ~= nil do 264 | msg = self:getMsg(subContext, xx .. " ", msg) 265 | subContext = subContext._nextContext 266 | end 267 | return msg 268 | end 269 | 270 | -------------------------- gt -------------------------- 271 | 272 | 273 | 274 | g_t.tabError = _{ 275 | s1 = g_t.empty_fun, 276 | event = g_t.empty_event, 277 | } 278 | 279 | g_t.tabSuccess = _{ 280 | s1 = function (c, skipFrame) 281 | c:output(true) 282 | if skipFrame then 283 | c:call(g_t.skipFrames(1), "s2") 284 | end 285 | end, 286 | } 287 | 288 | g_t.tabFail = _{ 289 | s1 = function (c, skipFrame) 290 | c:output(false) 291 | if skipFrame then 292 | c:call(g_t.skipFrames(1), "s2") 293 | end 294 | end, 295 | } 296 | 297 | 298 | g_t.tabForward = _{ 299 | s1 = function(c, ...) 300 | c:output(...) 301 | end 302 | } 303 | 304 | g_t.skipFrames = _{ 305 | s1 = function (c, totalFrames, tab) 306 | if g_t.debug then 307 | c._nickName = "skipFrames" 308 | end 309 | 310 | c.totalFrames = totalFrames or 1 311 | c.numFrames = 0 312 | end, 313 | 314 | update = function(c, dt) 315 | c.numFrames = c.numFrames + 1 316 | if c.numFrames >= c.totalFrames then 317 | c:stop() 318 | end 319 | end, 320 | 321 | __addNickName = function(c) 322 | c._nickName = "skipFrames<" .. (c.totalFrames) .. ">" 323 | end, 324 | 325 | } 326 | 327 | g_t.waitMessage = _{ 328 | s1 = function(c, msg) 329 | if g_t.debug then 330 | c._nickName = "waitMessage" 331 | end 332 | c.msg = msg 333 | 334 | c:registerMsg(msg, function(msg, data, ...) 335 | c:output(msg, data, ...) 336 | c:stop() 337 | end) 338 | end, 339 | 340 | __addNickName = function(c) 341 | c._nickName = "waitMessage<" .. (c.msg) .. ">" 342 | end, 343 | 344 | event = g_t.empty_event, 345 | } 346 | 347 | g_t.waitMessageWithFilter = _{ 348 | s1 = function(c, msg, filter) 349 | if g_t.debug then 350 | c._nickName = "waitMessageWithFilter" 351 | end 352 | c.msg = msg 353 | 354 | c:registerMsg(msg, function(msg, data, ...) 355 | local ok = true 356 | ok = filter(msg, data, ...) 357 | if ok then 358 | c:output(msg, data, ...) 359 | c:stop() 360 | end 361 | end) 362 | end, 363 | 364 | __addNickName = function(c) 365 | c._nickName = "waitMessageWithFilter<" .. (c.msg) .. ">" 366 | end, 367 | 368 | event = g_t.empty_event, 369 | } 370 | 371 | g_t.pageViewChange = _{ 372 | s1 = function(c, goTable, callBack) 373 | c.goTable = goTable 374 | c.isPage = goTable:com(ct.custom_pageView) ~= nil 375 | if c.isPage then 376 | goTable:addOnPageViewChangedListener(callBack) 377 | else 378 | goTable:addOnPageChangedListener(callBack) 379 | end 380 | end, 381 | event = g_t.empty_event, 382 | final = function(c) 383 | if c.isPage then 384 | c.goTable:ClearPageViewListener() 385 | else 386 | c.goTable:clearOnPageChanged() 387 | end 388 | end, 389 | 390 | __addNickName = function(c) 391 | c._nickName = "pageViewChange" 392 | end, 393 | } 394 | 395 | g_t.tabPageViewChange = _{ 396 | s1 = function(c, goTable) 397 | c.goTable = goTable 398 | c.isPage = goTable:com(ct.custom_pageView) ~= nil 399 | 400 | local onPageChage = function(index) c:upwardNotify("pageChange", index) end 401 | if c.isPage then 402 | goTable:addOnPageViewChangedListener(onPageChage) 403 | else 404 | goTable:addOnPageChangedListener(onPageChage) 405 | end 406 | end, 407 | 408 | event = g_t.empty_event, 409 | final = function(c) 410 | if c.isPage then 411 | c.goTable:ClearPageViewListener() 412 | else 413 | c.goTable:clearOnPageChanged() 414 | end 415 | end, 416 | 417 | __addNickName = function(c) 418 | c._nickName = "pageViewChange" 419 | end, 420 | } 421 | g_t.eventTrigger = _{ 422 | s1 = function(c, go, callback) 423 | c.trigger = go:GetComponent(typeof(EventTrigger)) 424 | if isNil(c.trigger) then 425 | c.trigger = go:AddComponent(typeof(EventTrigger)) 426 | end 427 | 428 | local downEntry, upEntry, dragEntry 429 | for i = 0, c.trigger.triggers.Count - 1 do 430 | local t = c.trigger.triggers[i] 431 | if t.eventID == EventTriggerType.PointerDown then 432 | downEntry = t 433 | elseif t.eventID == EventTriggerType.PointerUp then 434 | upEntry = t 435 | elseif t.eventID == EventTriggerType.Drag then 436 | dragEntry = t 437 | end 438 | end 439 | local createEntry = function(trigger, eventID) 440 | local entry = EventTrigger.Entry() 441 | entry.eventID = eventID 442 | entry.callback = EventTrigger.TriggerEvent() 443 | return entry 444 | end 445 | downEntry = downEntry or createEntry(c.trigger, EventTriggerType.PointerDown) 446 | upEntry = upEntry or createEntry(c.trigger, EventTriggerType.PointerUp) 447 | dragEntry = dragEntry or createEntry(c.trigger, EventTriggerType.Drag) 448 | 449 | downEntry.callback:AddListener( 450 | function() 451 | callback("down") 452 | end 453 | ) 454 | upEntry.callback:AddListener( 455 | function() 456 | callback("up") 457 | end 458 | ) 459 | dragEntry.callback:AddListener( 460 | function() 461 | callback("move") 462 | end 463 | ) 464 | c.trigger.triggers:Add(downEntry) 465 | c.trigger.triggers:Add(upEntry) 466 | c.trigger.triggers:Add(dragEntry) 467 | end, 468 | final = function(c) 469 | for _,v in pairs(c.trigger.triggers) do 470 | v.callback:RemoveAllListeners() 471 | v.callback:Invoke() 472 | end 473 | end, 474 | event = g_t.empty_event, 475 | 476 | __addNickName = function(c) 477 | c._nickName = "eventTrigger<" .. (c.trigger.name) .. ">" 478 | end, 479 | 480 | } 481 | 482 | -- compete中所有tab的第一个返回值必须是boolean, 并返回首个返回true的tab的索引及其返回值。若所有的tab执行结果都为false则返回nil 483 | function g_t.compete(tabs) 484 | return _{ 485 | s1 = function(c) 486 | if g_t.debug then 487 | c._nickName = "compete" 488 | end 489 | c.prefix = "__compete_sub" 490 | c.prefixLen = string.len(c.prefix) 491 | c.tabs = tabs 492 | c.faildCount = 0 493 | for k, tab in ipairs(c.tabs) do 494 | local name = c.prefix .. k 495 | c:registerLifeTimeListener(name, c) 496 | c:call(tab, name, g_t.anyOutputVars) 497 | end 498 | end, 499 | 500 | event = { 501 | [tabMachine.event_context_stop] = function(c, p, name, target) 502 | if name:find(c.prefix) then 503 | local index = name:sub(c.prefixLen + 1) 504 | if type(c.a1) ~= "boolean" then 505 | printError("compete中所有tab的第一个返回值必须是boolean") 506 | c:output(nil) 507 | c:stop() 508 | return 509 | end 510 | 511 | if c.a1 then 512 | c:output(index, c.a2, c.a3, c.a4, c.a5, c.a6, c.a7, c.a8, c.a9, c.a10) 513 | c:stop() 514 | else 515 | c.faildCount = c.faildCount + 1 516 | if c.faildCount >= table.nums(tabs) then 517 | c:output(nil) 518 | c:stop() 519 | end 520 | end 521 | end 522 | end 523 | }, 524 | 525 | __addNickName = function(c) 526 | c._nickName = "compete<" .. (#c.tabs) .. ">" 527 | end, 528 | } 529 | end 530 | 531 | 532 | g_t.tabCS = _{ 533 | s1 = function(c, csTab, reuse) 534 | c.csTab = csTab 535 | if reuse then 536 | c.csTab.IsReuse = reuse 537 | end 538 | c.csTab:_OnLuaStart(c) 539 | end, 540 | --private C# tab执行结束调用 541 | onCSStop = function(c) 542 | c.isCSStop = true 543 | c:stop() 544 | end, 545 | --private C# tab执行结束调用(带返回值) 546 | onCSReturn = function(c, outPutValue) 547 | c.isCSStop = true 548 | c:output(outPutValue) 549 | c:stop() 550 | end, 551 | onCSNotify = function(c, msg) 552 | c:upwardNotify("onCSNotify", msg) 553 | end, 554 | printCSTabError = function(c, ...) 555 | printError(..., (c and c.getDetailedPath and c:getDetailedPath()), debug.traceback("", 2)) 556 | end, 557 | event = g_t.empty_event, 558 | final = function(c) 559 | if not c.isCSStop then 560 | c.csTab:_OnLuaStop() 561 | end 562 | end, 563 | 564 | __addNickName = function(c) 565 | c._nickName = "tabCS" 566 | end, 567 | } 568 | 569 | g_t.tabUnityCoroutine = _{ 570 | s1 = function (c, asyncOperation, activeOnLoad) 571 | c._nickName = "asyncOperation" 572 | c.asyncOperation = asyncOperation 573 | if activeOnLoad == nil then activeOnLoad = true end 574 | c.activeOnLoad = activeOnLoad 575 | end, 576 | 577 | s1_event = g_t.empty_event, 578 | 579 | s1_update = function (c) 580 | if c.asyncOperation.IsDone==true then 581 | if c.asyncOperation:IsValid() then 582 | if c.asyncOperation.Status then 583 | if c.asyncOperation.Status==CS.UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.None then 584 | assert(false) 585 | elseif c.asyncOperation.Status==CS.UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded then 586 | if not c.activeOnLoad then 587 | c.asyncOperation.Result:ActivateAsync() 588 | c:stop("s1") 589 | return 590 | end 591 | c:output(true,c.asyncOperation) 592 | elseif c.asyncOperation.Status==CS.UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Failed then 593 | c:output(false) 594 | end 595 | c:stop() 596 | end 597 | else 598 | c:output(false) 599 | c:stop() 600 | end 601 | end 602 | end, 603 | 604 | s2 = g_t.empty_fun, 605 | 606 | s2_update = function(c) 607 | if c.asyncOperation.Result.Scene.isLoaded then 608 | c:output(true,c.asyncOperation) 609 | c:stop() 610 | end 611 | end, 612 | 613 | final = function(c) 614 | c.asyncOperation = nil 615 | end, 616 | 617 | __addNickName = function(c) 618 | c._nickName = "asyncOperation" 619 | end, 620 | } 621 | 622 | g_t.tabRequire = _{ 623 | s1 = function(c, luaFiles, frameTime) 624 | c.index = 1 625 | c.luaFiles = luaFiles 626 | c.totalCount = #luaFiles 627 | if (not frameTime) then 628 | frameTime = 0.003 629 | end 630 | c.frameTime = frameTime 631 | end, 632 | update = function(c) 633 | local startTime = socket:gettime() 634 | while(c.index <= c.totalCount) do 635 | require(c.luaFiles[c.index]) 636 | c.index = c.index + 1 637 | local endTime = socket:gettime() 638 | if (endTime - startTime >= c.frameTime) then 639 | return 640 | end 641 | end 642 | c:stop() 643 | end, 644 | } 645 | 646 | g_t.empty_tab = _{ 647 | s1 = function() 648 | end, 649 | } 650 | 651 | g_t.tabContainer = _{ 652 | s1 = g_t.empty_fun, 653 | event = g_t.empty_event, 654 | 655 | __addNickName = function(c) 656 | c._nickName = "tabContainer" 657 | end, 658 | } 659 | 660 | g_t.tabRefCount = _{ 661 | s1 = function(c) 662 | c.refMap = {} 663 | end, 664 | 665 | tabRef = _{ 666 | s1 = function(c, key) 667 | c.key = key 668 | c.count = 0 669 | end, 670 | event = { 671 | addRef = function(c, context) 672 | c.count = c.count + 1 673 | c:call(context:tabProxy(), "contentProxy") 674 | end, 675 | hasRef = function(c) 676 | return c:getSub("contentProxy") ~= nil 677 | end, 678 | }, 679 | contentProxy1 = function(c) 680 | c.count = c.count - 1 681 | if c.count == 0 then 682 | c:output(c.key) 683 | c:stop() 684 | end 685 | end, 686 | }, 687 | 688 | --public: 689 | acquire = function(c, context, key) 690 | if not c.refMap[key] then 691 | local refKeyTab = c:call(c.tabRef(key) >> "key", "ref") 692 | c.refMap[key] = refKeyTab 693 | end 694 | c.refMap[key]:notify("addRef", context) 695 | end, 696 | 697 | ref1 = function(c) 698 | c:upwardNotify("zero", c.key) 699 | c.refMap[c.key] = nil 700 | end, 701 | 702 | hasRef = function(c, key) 703 | if c.refMap[key] then 704 | return c.refMap[key]:notify("hasRef") 705 | end 706 | end, 707 | 708 | 709 | event = g_t.empty_event, 710 | } 711 | 712 | local aliveRefMetatable = nil 713 | aliveRefMetatable = { 714 | isAlive = function(self) 715 | local c = rawget(self, "c") 716 | if c == nil then 717 | return false 718 | end 719 | 720 | local lifeId = rawget(c, "__lifeId") 721 | local savedLifeId = rawget(self, "lifeId") 722 | 723 | if lifeId ~= savedLifeId then 724 | self.c = nil 725 | return false 726 | end 727 | 728 | return true 729 | end, 730 | 731 | getTarget = function(self) 732 | local c = rawget(self, "c") 733 | if c == nil then 734 | return nil 735 | end 736 | 737 | local lifeId = rawget(c, "__lifeId") 738 | local savedLifeId = rawget(self, "lifeId") 739 | 740 | if lifeId ~= savedLifeId then 741 | return nil 742 | end 743 | 744 | return c 745 | end, 746 | 747 | isTargetStopped = function(self) 748 | local target = self:getTarget() 749 | return target == nil or target:isStopped() 750 | end 751 | } 752 | 753 | aliveRefMetatable.__index = aliveRefMetatable 754 | 755 | function g_t.aliveRef(c) 756 | local t = {} 757 | t.c = c 758 | local lifeId = rawget(c, "__lifeId") 759 | assert(lifeId ~= nil) 760 | t.lifeId = lifeId 761 | setmetatable(t, aliveRefMetatable) 762 | 763 | return t 764 | end 765 | 766 | g_t.tabAliveMap = _{ 767 | s1 = function(c, keepRunning) 768 | c.keepRunning = keepRunning 769 | c._map = {} 770 | end, 771 | 772 | event = g_t.empty_event, 773 | 774 | --public: 775 | set = function (c, key, value) 776 | if value == nil then 777 | c._map[key] = value 778 | else 779 | local t = g_t.aliveRef(value) 780 | if c.keepRunning then 781 | if t:isAlive() and not value:isStopped() then 782 | c._map[key] = t 783 | end 784 | else 785 | c._map[key] = t 786 | end 787 | end 788 | end, 789 | 790 | get = function(c, key) 791 | local map = c._map 792 | local ref = map[key] 793 | if ref == nil then 794 | return nil 795 | end 796 | 797 | local target = ref:getTarget() 798 | if target == nil then 799 | map[key] = nil 800 | return nil 801 | end 802 | if c.keepRunning and target:isStopped() then 803 | map[key] = nil 804 | return nil 805 | end 806 | return target 807 | end, 808 | 809 | remove = function(c, key) 810 | c._map[key] = nil 811 | end, 812 | 813 | forEach = function(c, f, params) 814 | c:validate() 815 | local map = c._map 816 | for key, ref in pairs(map) do 817 | local target = ref:getTarget() 818 | local needToBreak = f(target, key, params) 819 | if needToBreak then 820 | return 821 | end 822 | end 823 | end, 824 | 825 | validate = function(c) 826 | local map = c._map 827 | for k, v in pairs(map) do 828 | if not v:isAlive() then 829 | map[k] = nil 830 | else 831 | if c.keepRunning and v:getTarget():isStopped() then 832 | map[k] = nil 833 | end 834 | end 835 | end 836 | end, 837 | 838 | count = function(c) 839 | c:validate() 840 | local num = 0 841 | for k, v in pairs(c._map) do 842 | num = num + 1 843 | end 844 | return num 845 | end, 846 | 847 | clear = function(c) 848 | local map = c._map 849 | for k, _ in pairs(map) do 850 | map[k] = nil 851 | end 852 | end, 853 | } 854 | 855 | g_t.tabRunningMap = #g_t.tabAliveMap(true) 856 | 857 | g_t.tabAliveList = _{ 858 | s1 = function(c, keepRunning) 859 | c._keepRunning = keepRunning 860 | c._array = {} 861 | end, 862 | 863 | event = g_t.empty_event, 864 | 865 | _checkContextValidate = function(c, aliveRef) 866 | if aliveRef == nil then 867 | return false 868 | end 869 | if not aliveRef:isAlive() then 870 | return false 871 | end 872 | if c._keepRunning then 873 | local t = aliveRef:getTarget() 874 | if t:isStopped() then 875 | return false 876 | else 877 | return true 878 | end 879 | else 880 | return true 881 | end 882 | end, 883 | 884 | --public: 885 | pushBack = function (c, context) 886 | local t = g_t.aliveRef(context) 887 | if c:_checkContextValidate(t) then 888 | table.insert(c._array, t) 889 | end 890 | end, 891 | 892 | pushFront = function(c, context) 893 | local t = g_t.aliveRef(context) 894 | if c:_checkContextValidate(t) then 895 | table.insert(c._array, 1, t) 896 | end 897 | end, 898 | 899 | popBack = function(c) 900 | local array = c._array 901 | while #array > 0 do 902 | local ref = table.remove(array) 903 | local target = ref:getTarget() 904 | if target ~= nil then 905 | return target 906 | end 907 | end 908 | end, 909 | 910 | popFront = function(c) 911 | local array = c._array 912 | while #array > 0 do 913 | local ref = table.remove(array, 1) 914 | local target = ref:getTarget() 915 | if target ~= nil then 916 | return target 917 | end 918 | end 919 | end, 920 | 921 | remove = function(c, context) 922 | local array = c._array 923 | local index = #array 924 | while index > 0 do 925 | local ref = array[index] 926 | local target = ref:getTarget() 927 | if target == context then 928 | table.remove(array, index) 929 | return target 930 | elseif target == nil then 931 | table.remove(array, index) 932 | if ref.c == context then 933 | return nil 934 | end 935 | end 936 | index = index - 1 937 | end 938 | end, 939 | 940 | forEach = function(c, f, params) 941 | c:validate() 942 | for _, ref in ipairs(c._array) do 943 | local target = ref:getTarget() 944 | local needToBreak = f(target, params) 945 | if needToBreak then 946 | return 947 | end 948 | end 949 | end, 950 | 951 | validate = function(c) 952 | local array = c._array 953 | local index = #array 954 | while index > 0 do 955 | local ref = array[index] 956 | if not c:_checkContextValidate(ref) then 957 | table.remove(array, index) 958 | end 959 | index = index - 1 960 | end 961 | end, 962 | 963 | count = function(c) 964 | c:validate() 965 | return #c._array 966 | end, 967 | 968 | clear = function(c) 969 | local array = c._array 970 | while #array > 0 do 971 | table.remove(array) 972 | end 973 | end, 974 | } 975 | 976 | g_t.tabRunningList = #g_t.tabAliveList(true) 977 | 978 | function g_t.importMethodsFromTab(hostTab, comName, comTab) 979 | for k, v in pairs(comTab) do 980 | if type(v) == "function" and not g_t.isInstructionTag(k) and not hostTab[k] then 981 | hostTab[k] = function(self, ...) 982 | local sc = self[comName] 983 | return v(sc, ...) 984 | end 985 | end 986 | end 987 | end 988 | 989 | function g_t.importMethodsFromList(hostTab, comName, methodNames) 990 | for _, methodName in ipairs(methodNames) do 991 | hostTab[methodName] = function(self, ...) 992 | local sc = self[comName] 993 | return sc[methodName](sc, ...) 994 | end 995 | end 996 | end 997 | 998 | function g_t.isTab(t) 999 | return t.__hooked == true 1000 | end 1001 | 1002 | function g_t.isInstructionTag(name) 1003 | local len = name:len() 1004 | local lastByte = name:byte(len) 1005 | 1006 | -- '0' = 48, '9' = 57 1007 | if lastByte >= 48 and lastByte <= 57 then 1008 | return true 1009 | end 1010 | 1011 | local splitPos = -1 1012 | local labels = tabMachine.labels 1013 | for _, labelLen in ipairs(tabMachine.labelLens) do 1014 | splitPos = len - labelLen 1015 | if splitPos <= 1 then 1016 | break 1017 | end 1018 | 1019 | -- '_' == 95 1020 | if name:byte(splitPos) == 95 then 1021 | break 1022 | end 1023 | 1024 | --make sure splitPos is also correct for last iteration 1025 | splitPos = -1 1026 | end 1027 | 1028 | if splitPos > 1 then 1029 | local label = tag:sub(splitPos + 1, -1) 1030 | return labels[label] ~= nil 1031 | elseif splitPos == 0 then 1032 | return labels[name] ~= nil 1033 | end 1034 | 1035 | return false 1036 | end 1037 | 1038 | g_t.tabMonitor = _{ 1039 | s1 = function(c) 1040 | if g_t.debug then 1041 | c:__addNickName() 1042 | end 1043 | end, 1044 | 1045 | event = g_t.empty_event, 1046 | 1047 | -- public: 1048 | watch = function (c, target, scName) 1049 | if target == nil then 1050 | return 1051 | end 1052 | c:call(target:tabProxy(scName), "watch") 1053 | end, 1054 | 1055 | isIdle = function (c) 1056 | return c:getSub("watch") == nil 1057 | end, 1058 | 1059 | waitIdle = function(c) 1060 | return c.tabWaitIdle(c) 1061 | end, 1062 | 1063 | --system: 1064 | __addNickName = function(c) 1065 | c._nickName = "tabMonitor" 1066 | end, 1067 | 1068 | tabWaitIdle = _{ 1069 | s1 = function(c, target) 1070 | c.target = target 1071 | end, 1072 | 1073 | update = function(c) 1074 | if c.target:isIdle() then 1075 | c:stop() 1076 | end 1077 | end, 1078 | } 1079 | } 1080 | 1081 | g_t.tabSimpleRequest = _{ 1082 | s1 = function(c, ip, port, reqMsg, respDecodeFunc, timeout) 1083 | c.ip = ip 1084 | c.port = port 1085 | c.reqMsg = reqMsg 1086 | c.socket = c:call(require("tabMachine.tabSocket"), "socket") 1087 | if timeout then 1088 | c:call(g_t.delay, "t1", nil, timeout) 1089 | end 1090 | if respDecodeFunc then 1091 | c.tabReadResponse = #c.socket:tabReadOneSegment(respDecodeFunc, nil, timeout and timeout or 10, true) 1092 | end 1093 | end, 1094 | 1095 | t2 = function(c) 1096 | c:output(nil, "Timeout") 1097 | c:stop() 1098 | end, 1099 | 1100 | s2 = function(c) 1101 | c:call(c.socket:connect(c.ip, c.port), "s3", {"isSuccess", "err"}) 1102 | end, 1103 | 1104 | s4 = function(c) 1105 | if c.isSuccess then 1106 | local tabSocket = require("tabMachine.tabSocket") 1107 | c:call(c.socket:tabInState(tabSocket.STATE.CONNECTED), "c1") 1108 | if c.tabReadResponse then 1109 | c.respMsg = nil 1110 | c:call(c.tabReadResponse, "s5", {"respMsg"}) 1111 | end 1112 | c.socket:send(c.reqMsg) 1113 | else 1114 | c:output(nil, c.err) 1115 | end 1116 | end, 1117 | 1118 | s6 = function(c) 1119 | c:output(c.respMsg) 1120 | c:stop() 1121 | end, 1122 | 1123 | c2 = function(c) 1124 | c:output(nil, "Disconnected") 1125 | c:stop() 1126 | end, 1127 | } 1128 | 1129 | g_t.tabWaitEmpty = _{ 1130 | s1 = function(c, target) 1131 | c.target = target 1132 | c:update() 1133 | end, 1134 | 1135 | update = function(c) 1136 | local target = c.target 1137 | if target.__subContexts == nil or #target.__subContexts == 0 then 1138 | c:stop() 1139 | end 1140 | end, 1141 | } 1142 | 1143 | g_t.tabSubLessEqual = _{ 1144 | s1 = function(c, target, count) 1145 | c.count = count 1146 | c.targetRef = g_t.aliveRef(target) 1147 | c:update() 1148 | end, 1149 | 1150 | update = function(c) 1151 | local target = c.targetRef:getTarget() 1152 | if not target then 1153 | c:stop() 1154 | end 1155 | if target.__subContexts == nil or #target.__subContexts <= c.count then 1156 | c:stop() 1157 | end 1158 | end, 1159 | } 1160 | 1161 | require("tabMachine.tabClicks") 1162 | require("tabMachine.tabAction") 1163 | require("tabMachine.tabLanes") 1164 | require("tabMachine.tabHttp") 1165 | 1166 | return cocosContext 1167 | --------------------------------------------------------------------------------