-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local TSM = select(2, ...) ---@type TSM local Publisher = TSM.Init("Util.ReactiveClasses.Publisher") ---@class Util.ReactiveClasses.Publisher local Environment = TSM.Include("Environment") local Math = TSM.Include("Util.Math") local String = TSM.Include("Util.String") local ObjectPool = TSM.Include("Util.ObjectPool") local EnumType = TSM.Include("Util.EnumType") local ReactivePublisher = TSM.Include("LibTSMClass").DefineClass("ReactivePublisher") ---@class ReactivePublisher local private = { publisherObjectPool = ObjectPool.New("PUBLISHER", ReactivePublisher, 1), keysTempTable = {}, } local STATE = EnumType.New("PUBLISHER_STATE", { INIT = EnumType.CreateValue(), ACQUIRED = EnumType.CreateValue(), STEPS = EnumType.CreateValue(), HANDLED = EnumType.CreateValue(), STORED = EnumType.CreateValue(), }) local STEP = EnumType.New("PUBLISHER_STEP", { MAP_WITH_FUNCTION = EnumType.CreateValue(), MAP_WITH_METHOD = EnumType.CreateValue(), MAP_WITH_KEY = EnumType.CreateValue(), MAP_WITH_KEY_COALESCED = EnumType.CreateValue(), MAP_BOOLEAN_WITH_VALUES = EnumType.CreateValue(), MAP_BOOLEAN_EQUALS = EnumType.CreateValue(), MAP_BOOLEAN_NOT_EQUALS = EnumType.CreateValue(), MAP_TO_BOOLEAN = EnumType.CreateValue(), MAP_TO_VALUE = EnumType.CreateValue(), INVERT_BOOLEAN = EnumType.CreateValue(), IGNORE_IF_KEY_EQUALS = EnumType.CreateValue(), IGNORE_IF_KEY_NOT_EQUALS = EnumType.CreateValue(), IGNORE_IF_NOT_KEY_IN_TABLE = EnumType.CreateValue(), IGNORE_IF_NOT_EQUALS = EnumType.CreateValue(), IGNORE_NIL = EnumType.CreateValue(), IGNORE_WITH_FUNCTION = EnumType.CreateValue(), IGNORE_DUPLICATES = EnumType.CreateValue(), IGNORE_DUPLICATES_WITH_KEYS = EnumType.CreateValue(), IGNORE_DUPLICATES_WITH_METHOD = EnumType.CreateValue(), PRINT = EnumType.CreateValue(), START_PROFILING = EnumType.CreateValue(), SHARE = EnumType.CreateValue(), CALL_METHOD = EnumType.CreateValue(), CALL_METHOD_IF_NOT_NIL = EnumType.CreateValue(), CALL_METHOD_FOR_EACH_LIST_VALUE = EnumType.CreateValue(), CALL_FUNCTION = EnumType.CreateValue(), UNPACK_AND_CALL_FUNCTION = EnumType.CreateValue(), UNPACK_AND_CALL_METHOD = EnumType.CreateValue(), ASSIGN_TO_TABLE_KEY = EnumType.CreateValue(), }) local STEP_RESULTING_STATE = { [STEP.MAP_WITH_FUNCTION] = STATE.STEPS, [STEP.MAP_WITH_METHOD] = STATE.STEPS, [STEP.MAP_WITH_KEY] = STATE.STEPS, [STEP.MAP_WITH_KEY_COALESCED] = STATE.STEPS, [STEP.MAP_BOOLEAN_WITH_VALUES] = STATE.STEPS, [STEP.MAP_BOOLEAN_EQUALS] = STATE.STEPS, [STEP.MAP_BOOLEAN_NOT_EQUALS] = STATE.STEPS, [STEP.MAP_TO_BOOLEAN] = STATE.STEPS, [STEP.MAP_TO_VALUE] = STATE.STEPS, [STEP.INVERT_BOOLEAN] = STATE.STEPS, [STEP.IGNORE_IF_KEY_EQUALS] = STATE.STEPS, [STEP.IGNORE_IF_KEY_NOT_EQUALS] = STATE.STEPS, [STEP.IGNORE_IF_NOT_KEY_IN_TABLE] = STATE.STEPS, [STEP.IGNORE_IF_NOT_EQUALS] = STATE.STEPS, [STEP.IGNORE_NIL] = STATE.STEPS, [STEP.IGNORE_WITH_FUNCTION] = STATE.STEPS, [STEP.IGNORE_DUPLICATES] = STATE.STEPS, [STEP.IGNORE_DUPLICATES_WITH_KEYS] = STATE.STEPS, [STEP.IGNORE_DUPLICATES_WITH_METHOD] = STATE.STEPS, [STEP.PRINT] = STATE.STEPS, [STEP.START_PROFILING] = STATE.STEPS, [STEP.SHARE] = STATE.STEPS, [STEP.CALL_METHOD] = STATE.HANDLED, [STEP.CALL_METHOD_IF_NOT_NIL] = STATE.HANDLED, [STEP.CALL_METHOD_FOR_EACH_LIST_VALUE] = STATE.HANDLED, [STEP.CALL_FUNCTION] = STATE.HANDLED, [STEP.UNPACK_AND_CALL_FUNCTION] = STATE.HANDLED, [STEP.UNPACK_AND_CALL_METHOD] = STATE.HANDLED, [STEP.ASSIGN_TO_TABLE_KEY] = STATE.HANDLED, } local OPTIMIZATION_IGNORED_STEPS = { [STEP.IGNORE_NIL] = true, [STEP.PRINT] = true, [STEP.START_PROFILING] = true, [STEP.MAP_TO_BOOLEAN] = true, [STEP.INVERT_BOOLEAN] = true, [STEP.SHARE] = true, [STEP.CALL_METHOD] = true, [STEP.CALL_METHOD_IF_NOT_NIL] = true, [STEP.CALL_METHOD_FOR_EACH_LIST_VALUE] = true, [STEP.CALL_FUNCTION] = true, [STEP.UNPACK_AND_CALL_FUNCTION] = true, [STEP.UNPACK_AND_CALL_METHOD] = true, [STEP.ASSIGN_TO_TABLE_KEY] = true, } local KEYS_SEP = "\001" local STEP_DATA_SIZE = 3 -- ============================================================================ -- Module Functions -- ============================================================================ ---Gets a publisher object. ---@return ReactivePublisher function Publisher.Get() return private.publisherObjectPool:Get() end ---Releases a publisher object. ---@param publisher ReactivePublisher The publisher object function Publisher.Release(publisher) publisher:_Release() private.publisherObjectPool:Recycle(publisher) end -- ============================================================================ -- Class Meta Methods -- ============================================================================ function ReactivePublisher:__init() self._subject = nil self._state = STATE.INIT self._shareStep = nil self._stepData = {} self._numSteps = 0 self._optimizeKeys = {} end function ReactivePublisher:_Acquire(subject) self._subject = subject self._state = STATE.ACQUIRED end function ReactivePublisher:_Release() assert(self._state == STATE.STORED) self._state = STATE.INIT self._subject = nil self._numSteps = 0 self._shareStep = nil wipe(self._stepData) wipe(self._optimizeKeys) end -- ============================================================================ -- Public Class Methods -- ============================================================================ ---Map published values to another value using a function. ---@param func fun(value: any, arg: any): any The mapping function which takes the published values and returns the results ---@param arg any An additional argument to pass to the function ---@return ReactivePublisher function ReactivePublisher:MapWithFunction(func, arg) assert(type(func) == "function") return self:_AddStepHelper(STEP.MAP_WITH_FUNCTION, func, arg) end ---Maps published values by calling a method on it. ---@param method string The name of the method to call on the published values ---@return ReactivePublisher function ReactivePublisher:MapWithMethod(method) assert(type(method) == "string") return self:_AddStepHelper(STEP.MAP_WITH_METHOD, method) end ---Maps published values by indexing it with the specified key. ---@param key string|number key The key to index the published values with ---@return ReactivePublisher function ReactivePublisher:MapWithKey(key) assert(type(key) == "string" or type(key) == "number") return self:_AddStepHelper(STEP.MAP_WITH_KEY, key) end ---Map published values by indexing it with two keys, keeping the first value one which is non-nil. ---@param key1 string The first key to index the published values with ---@param key2 string The second key to index the published values with ---@return ReactivePublisher function ReactivePublisher:MapWithKeyCoalesced(key1, key2) return self:_AddStepHelper(STEP.MAP_WITH_KEY_COALESCED, key1, key2) end ---Map published boolean values to the specified true / false values. ---@param trueValue any The value to map to if true ---@param falseValue any The value to map to if false ---@return ReactivePublisher function ReactivePublisher:MapBooleanWithValues(trueValue, falseValue) return self:_AddStepHelper(STEP.MAP_BOOLEAN_WITH_VALUES, trueValue, falseValue) end ---Map published values to a boolean based on whether or not it equals the specified value. ---@param value any The value to compare with ---@return ReactivePublisher function ReactivePublisher:MapBooleanEquals(value) return self:_AddStepHelper(STEP.MAP_BOOLEAN_EQUALS, value) end ---Map published values to a boolean based on whether or not it equals the specified value. ---@param value any The value to compare with ---@return ReactivePublisher function ReactivePublisher:MapBooleanNotEquals(value) return self:_AddStepHelper(STEP.MAP_BOOLEAN_NOT_EQUALS, value) end ---Map published values to a boolean based on whether or not it's truthy. ---@return ReactivePublisher function ReactivePublisher:MapToBoolean() return self:_AddStepHelper(STEP.MAP_TO_BOOLEAN) end ---Map published values to a specific value. ---@param value any The value to map to ---@return ReactivePublisher function ReactivePublisher:MapToValue(value) return self:_AddStepHelper(STEP.MAP_TO_VALUE, value) end ---Invert published boolean values. ---@return ReactivePublisher function ReactivePublisher:InvertBoolean() return self:_AddStepHelper(STEP.INVERT_BOOLEAN) end ---Ignores published values where a specified key equals the specified value. ---@param key string The key to compare at ---@param value any The value to compare with ---@return ReactivePublisher function ReactivePublisher:IgnoreIfKeyEquals(key, value) assert(type(key) == "string" or type(key) == "number") return self:_AddStepHelper(STEP.IGNORE_IF_KEY_EQUALS, key, value) end ---Ignores published values where a specified key does not equal the specified value. ---@param key string The key to compare at ---@param value any The value to compare with ---@return ReactivePublisher function ReactivePublisher:IgnoreIfKeyNotEquals(key, value) assert(type(key) == "string" or type(key) == "number") return self:_AddStepHelper(STEP.IGNORE_IF_KEY_NOT_EQUALS, key, value) end ---Ignores published values which don't exist as a key within the specified table. ---@param tbl table The table to check within ---@return ReactivePublisher function ReactivePublisher:IgnoreIfNotKeyInTable(tbl) assert(type(tbl) == "table") return self:_AddStepHelper(STEP.IGNORE_IF_NOT_KEY_IN_TABLE, tbl) end ---Ignores published values which don't equal the specified value. ---@param value any The value to compare against ---@return ReactivePublisher function ReactivePublisher:IgnoreIfNotEquals(value) return self:_AddStepHelper(STEP.IGNORE_IF_NOT_EQUALS, value) end ---Ignores published values if it's nil. ---@return ReactivePublisher function ReactivePublisher:IgnoreNil() return self:_AddStepHelper(STEP.IGNORE_NIL) end ---Ignores published values if the specified function doesn't return true. ---@param func fun(value: any): boolean The function which takes the published values and returns true/false ---@return ReactivePublisher function ReactivePublisher:IgnoreWithFunction(func) assert(type(func) == "function") return self:_AddStepHelper(STEP.IGNORE_WITH_FUNCTION, func) end ---Ignores duplicate published values. ---@return ReactivePublisher function ReactivePublisher:IgnoreDuplicates() return self:_AddStepHelper(STEP.IGNORE_DUPLICATES) end ---Ignores duplicate published values by checking the specified keys. ---@param ... string Keys to compare to detect duplicate published values ---@return ReactivePublisher function ReactivePublisher:IgnoreDuplicatesWithKeys(...) local keys = private.ToKeysStr(...) assert(keys ~= "") return self:_AddStepHelper(STEP.IGNORE_DUPLICATES_WITH_KEYS, keys) end ---Ignores duplicate published values by calling the specified method. ---@param method string The method to call on the published values ---@return ReactivePublisher function ReactivePublisher:IgnoreDuplicatesWithMethod(method) assert(type(method) == "string") return self:_AddStepHelper(STEP.IGNORE_DUPLICATES_WITH_METHOD, method) end ---Prints published values and passes them through for debugging purposes. ---@param tag? string An optional tag to add to the prints ---@return ReactivePublisher function ReactivePublisher:Print(tag) assert(Environment.IsDev()) return self:_AddStepHelper(STEP.PRINT, tag) end ---Wraps all following steps in profiling nodes. ---@param prefix string A prefix to use for the profiling nodes ---@return ReactivePublisher function ReactivePublisher:StartProfiling(prefix) assert(Environment.IsDev()) return self:_AddStepHelper(STEP.START_PROFILING, prefix) end ---Shares the result of the publisher at the current point in the chain. ---@param numPublishers number The number of child publisher chains to share with ---@return ReactivePublisher function ReactivePublisher:Share(numPublishers) assert(not self._shareStep and numPublishers > 1) self:_AddStepHelper(STEP.SHARE, numPublishers) self._shareStep = self._numSteps return self end ---Calls a method with the published values. ---@param obj table The object to call the method on ---@param method string The name of the method to call with the published values ---@return ReactivePublisher function ReactivePublisher:CallMethod(obj, method) assert(type(obj) == "table" and type(method) == "string") return self:_AddStepHelper(STEP.CALL_METHOD, obj, method) end ---Calls a method with the published values if it's non-nil. ---@param obj table The object to call the method on ---@param method string The name of the method to call with the published values ---@return ReactivePublisher function ReactivePublisher:CallMethodIfNotNil(obj, method) assert(type(obj) == "table" and type(method) == "string") return self:_AddStepHelper(STEP.CALL_METHOD_IF_NOT_NIL, obj, method) end ---Calls a method with the published values on each value in a list. ---@param list table The list of objects to call the method on ---@param method string The name of the method to call with the published values ---@return ReactivePublisher function ReactivePublisher:CallMethodForEachListValue(list, method) assert(type(list) == "table" and type(method) == "string") return self:_AddStepHelper(STEP.CALL_METHOD_FOR_EACH_LIST_VALUE, list, method) end ---Calls a function with the published values. ---@param func fun(value: any) The function to call with the published values ---@return ReactivePublisher function ReactivePublisher:CallFunction(func) assert(type(func) == "function") return self:_AddStepHelper(STEP.CALL_FUNCTION, func) end ---Unpacks published values and then calls a function with the result. ---@param func fun(value: any) The function to call with the unpacked published values ---@return ReactivePublisher function ReactivePublisher:UnpackAndCallFunction(func) assert(type(func) == "function") return self:_AddStepHelper(STEP.UNPACK_AND_CALL_FUNCTION, func) end ---Unpacks published values and then calls a method with the result. ---@param obj table The object to call the method on ---@param method string The name of the method to call with the unpacked published values ---@return ReactivePublisher function ReactivePublisher:UnpackAndCallMethod(obj, method) assert(type(obj) == "table" and type(method) == "string") return self:_AddStepHelper(STEP.UNPACK_AND_CALL_METHOD, obj, method) end ---Assigns published values to the specified key in the table. ---@param tbl table The table to assign the published values into ---@param key string The key to assign the published values at ---@return ReactivePublisher function ReactivePublisher:AssignToTableKey(tbl, key) assert(type(tbl) == "table" and type(key) == "string") return self:_AddStepHelper(STEP.ASSIGN_TO_TABLE_KEY, tbl, key) end ---Stores the publisher in a table for later cancelling. ---@param tbl table The table to assign the published values into ---@param key string The key to assign the published values at function ReactivePublisher:StoreIn(tbl) assert(type(tbl) == "table") tinsert(tbl, self) self:_Commit() end ---Marks a publisher as stored manually by the caller. ---@return ReactivePublisher function ReactivePublisher:Stored() self:_Commit() return self end ---Cancels and releases a publisher. function ReactivePublisher:Cancel() assert(self._state == STATE.STORED) self._subject:_HandlePublisherEvent(self, "OnCancel") self:_Release() private.publisherObjectPool:Recycle(self) end -- ============================================================================ -- Private Class Methods -- ============================================================================ function ReactivePublisher:_AddStepHelper(stepType, arg1, arg2) local newState = STEP_RESULTING_STATE[stepType] assert(newState == STATE.STEPS or newState == STATE.HANDLED) assert(self._state == STATE.ACQUIRED or self._state == STATE.STEPS) if newState == STATE.HANDLED and self._shareStep then -- The first arg of the share step contains the number of publishers we're sharing with local numPublishersIndex = (self._shareStep - 1) * STEP_DATA_SIZE + 2 self._stepData[numPublishersIndex] = self._stepData[numPublishersIndex] - 1 assert(self._stepData[numPublishersIndex] >= 0) if self._stepData[numPublishersIndex] > 0 then -- There are still more handlers to be added newState = STATE.STEPS end end self._state = newState assert(STEP_DATA_SIZE == 3) self._stepData[self._numSteps * STEP_DATA_SIZE + 1] = stepType self._stepData[self._numSteps * STEP_DATA_SIZE + 2] = arg1 self._stepData[self._numSteps * STEP_DATA_SIZE + 3] = arg2 self._numSteps = self._numSteps + 1 if self._state == STATE.HANDLED then self._subject:_HandlePublisherEvent(self, "OnHandled") end return self end function ReactivePublisher:_Commit() assert(self._state == STATE.HANDLED) self._state = STATE.STORED -- Perform optimizations assert(not next(self._optimizeKeys)) local didOptimize = false for step = 1, self._numSteps do local stepType, arg1, arg2 = unpack(self._stepData, (step - 1) * STEP_DATA_SIZE + 1, step * STEP_DATA_SIZE) if stepType == STEP.MAP_WITH_KEY or stepType == STEP.IGNORE_IF_KEY_EQUALS or stepType == STEP.IGNORE_IF_KEY_NOT_EQUALS then self._optimizeKeys[arg1] = true elseif stepType == STEP.MAP_WITH_KEY_COALESCED then self._optimizeKeys[arg1] = true self._optimizeKeys[arg2] = true elseif stepType == STEP.IGNORE_DUPLICATES or stepType == STEP.MAP_TO_VALUE or stepType == STEP.IGNORE_DUPLICATES_WITH_METHOD then didOptimize = true break elseif stepType == STEP.IGNORE_DUPLICATES_WITH_KEYS then for key in String.SplitIterator(arg1, KEYS_SEP) do self._optimizeKeys[key] = true end didOptimize = true break elseif OPTIMIZATION_IGNORED_STEPS[stepType] then -- Ignore these steps for optimizations elseif stepType == STEP.MAP_WITH_FUNCTION or stepType == STEP.MAP_WITH_METHOD or stepType == STEP.MAP_BOOLEAN_WITH_VALUES or stepType == STEP.MAP_BOOLEAN_EQUALS or stepType == STEP.MAP_BOOLEAN_NOT_EQUALS or stepType == STEP.IGNORE_IF_NOT_KEY_IN_TABLE or stepType == STEP.IGNORE_IF_NOT_EQUALS or stepType == STEP.IGNORE_WITH_FUNCTION then -- Not able to optimize didOptimize = false break else error("Invalid stepType: "..tostring(stepType)) end end if not didOptimize then wipe(self._optimizeKeys) end self._subject:_HandlePublisherEvent(self, "OnCommit", didOptimize) end function ReactivePublisher:_HandleData(data, optimizeKey) if self._state ~= STATE.STORED then error("Invalid publisher state") end if optimizeKey and next(self._optimizeKeys) and not self._optimizeKeys[optimizeKey] then return end local profilingPrefix = nil local step = 0 while true do step = step + 1 if step > self._numSteps then error("Publisher did not terminate") end local stepType, arg1, arg2 = unpack(self._stepData, (step - 1) * STEP_DATA_SIZE + 1, step * STEP_DATA_SIZE) local arg2Index = step * STEP_DATA_SIZE local stepProfilingNode = profilingPrefix and profilingPrefix.."_"..stepType or nil if stepProfilingNode then TSMDEV.Profiling.StartNode(stepProfilingNode) end local ignoreValue = false if stepType == STEP.MAP_WITH_FUNCTION then data = arg1(data, arg2) elseif stepType == STEP.MAP_WITH_METHOD then data = data[arg1](data) elseif stepType == STEP.MAP_WITH_KEY then data = data[arg1] elseif stepType == STEP.MAP_WITH_KEY_COALESCED then local newData = data[arg1] if newData == nil then newData = data[arg2] end data = newData elseif stepType == STEP.MAP_BOOLEAN_WITH_VALUES then if data then data = arg1 else data = arg2 end elseif stepType == STEP.MAP_BOOLEAN_EQUALS then data = data == arg1 elseif stepType == STEP.MAP_BOOLEAN_NOT_EQUALS then data = data ~= arg1 elseif stepType == STEP.MAP_TO_BOOLEAN then data = data and true or false elseif stepType == STEP.MAP_TO_VALUE then data = arg1 elseif stepType == STEP.INVERT_BOOLEAN then if type(data) ~= "boolean" then error("Invalid data type: "..tostring(data)) end data = not data elseif stepType == STEP.IGNORE_IF_KEY_EQUALS then if data[arg1] == arg2 then ignoreValue = true end elseif stepType == STEP.IGNORE_IF_KEY_NOT_EQUALS then if data[arg1] ~= arg2 then ignoreValue = true end elseif stepType == STEP.IGNORE_IF_NOT_KEY_IN_TABLE then if arg1[data] == nil then ignoreValue = true end elseif stepType == STEP.IGNORE_IF_NOT_EQUALS then if data ~= arg1 then ignoreValue = true end elseif stepType == STEP.IGNORE_NIL then if data == nil then ignoreValue = true end elseif stepType == STEP.IGNORE_WITH_FUNCTION then local result = arg1(data) if result == false then ignoreValue = true elseif result ~= true then error("Invalid IgnoreWithFunction result: "..tostring(result)) end elseif stepType == STEP.IGNORE_DUPLICATES then local value = data if type(value) == "table" then value = tostring(value) end local hash = Math.CalculateHash(value) -- We use stepArg2 to store the previous hash if hash == arg2 then ignoreValue = true else self._stepData[arg2Index] = hash end elseif stepType == STEP.IGNORE_DUPLICATES_WITH_KEYS then local hash = nil for key in String.SplitIterator(arg1, KEYS_SEP) do local value = data[key] if type(value) == "table" then value = tostring(value) end hash = Math.CalculateHash(value, hash) end -- We use stepArg2 to store the previous hash if hash == arg2 then ignoreValue = true else self._stepData[arg2Index] = hash end elseif stepType == STEP.IGNORE_DUPLICATES_WITH_METHOD then local hash = data[arg1](data) -- We use stepArg2 to store the previous hash if hash == arg2 then ignoreValue = true else self._stepData[arg2Index] = hash end elseif stepType == STEP.PRINT then if arg1 then print(format("Published value (%s):", tostring(arg1))) else print("Published value:") end TSMDEV.Dump(data) elseif stepType == STEP.START_PROFILING then assert(not profilingPrefix) profilingPrefix = arg1 TSMDEV.Profiling.StartNode(profilingPrefix) elseif stepType == STEP.SHARE then if step ~= self._shareStep then error("Invalid share step") end -- Store the shared value in stepArg2 self._stepData[arg2Index] = data elseif stepType == STEP.CALL_METHOD then arg1[arg2](arg1, data) elseif stepType == STEP.CALL_METHOD_IF_NOT_NIL then local func = arg1[arg2] if func ~= nil then func(arg1, data) end elseif stepType == STEP.CALL_METHOD_FOR_EACH_LIST_VALUE then for j = 1, #arg1 do local obj = arg1[j] obj[arg2](obj, data) end elseif stepType == STEP.CALL_FUNCTION then arg1(data) elseif stepType == STEP.UNPACK_AND_CALL_FUNCTION then arg1(unpack(data)) elseif stepType == STEP.UNPACK_AND_CALL_METHOD then arg1[arg2](arg1, unpack(data)) elseif stepType == STEP.ASSIGN_TO_TABLE_KEY then arg1[arg2] = data else error("Invalid stepType: "..tostring(stepType)) end if stepProfilingNode then TSMDEV.Profiling.EndNode(stepProfilingNode) end if ignoreValue then if self._shareStep and step < self._shareStep then -- Ignoring before the share step, so we're done break elseif self._shareStep then -- Advance past the current handler to the next child publisher chain with the shared value while true do local newStepType = self._stepData[step * STEP_DATA_SIZE + 1] step = step + 1 if not newStepType then error("Could not find handling step") elseif STEP_RESULTING_STATE[newStepType] == STATE.HANDLED then step = step + 1 break end end if step > self._numSteps then -- No more child publisher chains, so we're done break end data = self._stepData[self._shareStep * STEP_DATA_SIZE] else break end elseif STEP_RESULTING_STATE[stepType] == STATE.HANDLED then if step == self._numSteps then -- We're done break elseif self._shareStep then -- Replace the current data with the shared value and continue data = self._stepData[self._shareStep * STEP_DATA_SIZE] else error("Extra steps") end end end if profilingPrefix then TSMDEV.Profiling.EndNode(profilingPrefix) end end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.ToKeysStr(...) assert(not next(private.keysTempTable)) for i = 1, select("#", ...) do local key = select(i, ...) assert(type(key) == "string" and key ~= "" and not strfind(key, KEYS_SEP)) private.keysTempTable[i] = key end local result = table.concat(private.keysTempTable, KEYS_SEP) wipe(private.keysTempTable) return result end