diff --git a/IIfA/IIfA.lua b/IIfA/IIfA.lua
index bc65109..d9cc03d 100644
--- a/IIfA/IIfA.lua
+++ b/IIfA/IIfA.lua
@@ -469,12 +469,18 @@ function IIfA_onLoad(eventCode, addOnName)
if not ObjSettings.frameSettings.hud.hidden then
IIfA:ProcessSceneChange("hud", "showing", "shown")
end
- -- IIfA:MakeBSI()
IIfA:RegisterForEvents()
IIfA:RegisterForSceneChanges() -- register for callbacks on scene statechanges using user preferences or defaults
- IIfA:ScanCurrentCharacter()
+
IIfA:ScanBank()
+ IIfA:ScanCurrentCharacter()
+ zo_callLater(function()
+ IIfA:MakeBSI()
+ end, 5000)
+
+
+
end
EVENT_MANAGER:RegisterForEvent("IIfALoaded", EVENT_ADD_ON_LOADED, IIfA_onLoad)
diff --git a/IIfA/IIfA.txt b/IIfA/IIfA.txt
index 4a373d1..3f80d11 100644
--- a/IIfA/IIfA.txt
+++ b/IIfA/IIfA.txt
@@ -26,6 +26,7 @@ libs\LibAddonMenu-2.0\controls\texture.lua
libs\LibAddonMenu-2.0\controls\iconpicker.lua
libs\LibAddonMenu-2.0\controls\divider.lua
libs\LibCustomMenu\LibCustomMenu.lua
+libs\LibAsync\LibAsync.lua
IIfA_Preload.lua
diff --git a/IIfA/IIfADataCollection.lua b/IIfA/IIfADataCollection.lua
index 99303b2..4b7c2f9 100644
--- a/IIfA/IIfADataCollection.lua
+++ b/IIfA/IIfADataCollection.lua
@@ -1,7 +1,8 @@
-local IIfA = IIfA
-local EMPTY_STRING = ""
+local IIfA = IIfA
+local EMPTY_STRING = ""
-
+local task = LibStub("LibAsync"):Create("IIfA_DataCollection")
+IIfA.task = task
local function grabBagContent(bagId)
local bagItems = GetBagSize(bagId)
for slotNum=0, bagItems, 1 do
@@ -64,15 +65,19 @@ function IIfA:CollectGuildBank()
IIfA.data.guildBanks[curGuild] = {}
IIfA.data.guildBanks[curGuild].bCollectData = true -- default to true just so it's here and ok
end
- local guildData = IIfA.data.guildBanks[curGuild]
- guildData.items = #ZO_GuildBankBackpack.data
- guildData.lastCollected = GetDate() .. "@" .. GetFormattedTime();
- IIfA:ClearLocationData(curGuild)
- IIfA:DebugOut(" - " .. #ZO_GuildBankBackpack.data .. " items")
- for i=1, #ZO_GuildBankBackpack.data do
- local slotIndex = ZO_GuildBankBackpack.data[i].data.slotIndex
- IIfA:EvalBagItem(BAG_GUILDBANK, slotIndex)
- end
+
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ local guildData = IIfA.data.guildBanks[curGuild]
+ guildData.items = #ZO_GuildBankBackpack.data
+ guildData.lastCollected = GetDate() .. "@" .. GetFormattedTime();
+ IIfA:ClearLocationData(curGuild)
+ IIfA:DebugOut(" - " .. #ZO_GuildBankBackpack.data .. " items")
+ for i=1, #ZO_GuildBankBackpack.data do
+ local slotIndex = ZO_GuildBankBackpack.data[i].data.slotIndex
+ IIfA:EvalBagItem(BAG_GUILDBANK, slotIndex)
+ end
+ end)
-- d("IIfA - Guild Bank Collected - " .. curGuild)
end
@@ -87,11 +92,20 @@ local function scanBags()
IIfA:ClearLocationData(IIfA.currentCharacterId)
if not IIfA:IsCharacterEquipIgnored(playerName) then
- grabBagContent(BAG_WORN)
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ grabBagContent(BAG_WORN)
+ end)
end
if not IIfA:IsCharacterInventoryIgnored(playerName) then
- grabBagContent(BAG_BACKPACK)
- end
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ grabBagContent(BAG_BACKPACK)
+ end)
+ end
+ task:Call(function()
+ IIfA:MakeBSI()
+ end)
end
IIfA.ScanCurrentCharacter = scanBags
@@ -105,8 +119,12 @@ local function tryScanHouseBank()
if IsCollectibleUnlocked(collectibleId) then
local collectibleName = GetCollectibleNickname(collectibleId)
if collectibleName == EMPTY_STRING then collectibleName = GetCollectibleName(collectibleId) end
- IIfA:ClearLocationData(bagId)
- grabBagContent(bagId)
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:ClearLocationData(bagId)
+ end):Then(function()
+ grabBagContent(bagId)
+ end)
end
return true
@@ -114,25 +132,46 @@ end
function IIfA:ScanBank()
if tryScanHouseBank() then return end
- IIfA:ClearLocationData(GetString(IIFA_BAG_BANK))
- grabBagContent(BAG_BANK)
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:ClearLocationData(GetString(IIFA_BAG_BANK))
+ end):Then(function()
+ grabBagContent(BAG_BANK)
+ end)
local slotNum = nil
if HasCraftBagAccess() then
- grabBagContent(BAG_SUBSCRIBER_BANK)
- IIfA:ClearLocationData(GetString(IIFA_BAG_CRAFTBAG))
- slotNum = GetNextVirtualBagSlotId(slotNum)
- while slotNum ~= nil do
- IIfA:EvalBagItem(BAG_VIRTUAL, slotNum)
+ task:Call(function()
+ grabBagContent(BAG_SUBSCRIBER_BANK)
+ end):Then(function()
+ IIfA:ClearLocationData(GetString(IIFA_BAG_CRAFTBAG))
+ end):Then(function()
slotNum = GetNextVirtualBagSlotId(slotNum)
- end
+ while slotNum ~= nil do
+ IIfA:EvalBagItem(BAG_VIRTUAL, slotNum)
+ slotNum = GetNextVirtualBagSlotId(slotNum)
+ end
+ end)
end
+
end
+function IIfA:UpdateBSI(bagId, slotId, locationId)
+ if locationId then
+ IIfA:MakeBSI()
+ elseif nil ~= bagId and nil ~= slotId and nil ~= IIfA.BagSlotInfo[bagId] then
+ IIfA.BagSlotInfo[bagId][slotId] = GetItemLink(bagId, slotId)
+ end
+end
+
+
-- only grabs the content of bagpack and worn on the first login - hence we set the function to insta-return below.
function IIfA:OnFirstInventoryOpen()
scanBags()
- IIfA:ScanBank()
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:ScanBank()
+ end)
IIfA.OnFirstInventoryOpen = function() return end
end
@@ -183,37 +222,49 @@ function IIfA:CleanEmptyGuildBug()
end
function IIfA:GuildBankReady()
- IIfA:DebugOut("GuildBankReady...")
- IIfA.isGuildBankReady = false
- IIfA:UpdateGuildBankData()
- IIfA:CleanEmptyGuildBug()
- IIfA:CollectGuildBank()
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:DebugOut("GuildBankReady...")
+ IIfA.isGuildBankReady = false
+ IIfA:UpdateGuildBankData()
+ end):Then(function()
+ IIfA:CleanEmptyGuildBug()
+ end):Then(function()
+ IIfA:CollectGuildBank()
+ end)
end
function IIfA:GuildBankDelayReady()
IIfA:DebugOut("GuildBankDelayReady...")
if not IIfA.isGuildBankReady then
IIfA.isGuildBankReady = true
- zo_callLater(function() IIfA:GuildBankReady() end, 1750)
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:GuildBankReady()
+ end)
end
end
function IIfA:GuildBankAddRemove(eventID, slotNum)
IIfA:DebugOut("Guild Bank Add or Remove...")
- IIfA:UpdateGuildBankData()
- IIfA:CleanEmptyGuildBug()
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ IIfA:UpdateGuildBankData()
+ IIfA:CleanEmptyGuildBug()
+ end):Then(function()
--IIfA:CollectGuildBank()
- local dbItem, itemKey
- if eventID == EVENT_GUILD_BANK_ITEM_ADDED then
--- d("GB Add")
- dbItem, itemKey = IIfA:EvalBagItem(BAG_GUILDBANK, slotNum, true, 0)
- IIfA:ValidateItemCounts(BAG_GUILDBANK, slotNum, dbItem, itemKey)
- else
--- d("GB Remove")
--- d(GetItemLink(BAG_GUILDBANK, slotNum))
--- dbItem, itemKey = IIfA:EvalBagItem(BAG_BACKPACK, slotNum)
--- IIfA:ValidateItemCounts(BAG_GUILDBANK, slotNum, dbItem, itemKey)
- end
+ local dbItem, itemKey
+ if eventID == EVENT_GUILD_BANK_ITEM_ADDED then
+ -- d("GB Add")
+ dbItem, itemKey = IIfA:EvalBagItem(BAG_GUILDBANK, slotNum, true, 0)
+ IIfA:ValidateItemCounts(BAG_GUILDBANK, slotNum, dbItem, itemKey)
+ else
+ -- d("GB Remove")
+ -- d(GetItemLink(BAG_GUILDBANK, slotNum))
+ -- dbItem, itemKey = IIfA:EvalBagItem(BAG_BACKPACK, slotNum)
+ -- IIfA:ValidateItemCounts(BAG_GUILDBANK, slotNum, dbItem, itemKey)
+ end
+ end)
end
function IIfA:ActionLayerInventoryUpdate()
@@ -244,7 +295,8 @@ function IIfA:InventorySlotUpdate(eventCode, bagId, slotNum, isNewItem, itemSoun
isNewItem = "False"
end
- IIfA:DebugOut(zo_strformat("Inv Slot Upd - <<1>>, <<2>>, <<3>>, <<4>>, <<5>>, <<6>>, <<7>>", GetItemLink(bagId, slotNum, LINK_STYLE_NORMAL), eventCode, bagId, slotNum, inventoryUpdateReason, qty, isNewItem))
+ IIfA:DebugOut(zo_strformat("Inv Slot Upd - <<1>>, <<2>>, <<3>>, <<4>>, <<5>>, <<6>>, <<7>>",
+ GetItemLink(bagId, slotNum, LINK_STYLE_NORMAL), eventCode, bagId, slotNum, inventoryUpdateReason, qty, isNewItem))
local dbItem, itemKey
dbItem, itemKey = self:EvalBagItem(bagId, slotNum, true, qty)
-- if dbItem ~= nil and (bagId == BAG_BANK or bagId == BAG_SUBSCRIBER_BANK or bagId == BAG_BACKPACK) then
@@ -261,8 +313,7 @@ function IIfA:RescanHouse(houseCollectibleId)
if not IIfA:GetTrackedBags()[houseCollectibleId] then return end
- -- it's easier to throw everything away and re-scan than to conditionally update
- IIfA:ClearLocationData(houseCollectibleId)
+
local function getAllPlacedFurniture()
local ret = {}
@@ -278,10 +329,16 @@ function IIfA:RescanHouse(houseCollectibleId)
end
end
- local items = getAllPlacedFurniture()
- for itemLink, itemCount in pairs(items) do
- IIfA:AddFurnitureItem(itemLink, itemCount, houseCollectibleId, true)
- end
+ -- call with libAsync to avoid lags
+ task:Call(function()
+ -- it's easier to throw everything away and re-scan than to conditionally update
+ IIfA:ClearLocationData(houseCollectibleId)
+ end):Then(function()
+ local items = getAllPlacedFurniture()
+ for itemLink, itemCount in pairs(items) do
+ IIfA:AddFurnitureItem(itemLink, itemCount, houseCollectibleId, true)
+ end
+ end)
end
@@ -330,8 +387,15 @@ function IIfA:EvalBagItem(bagId, slotNum, fromXfer, itemCount, itemLink, itemNam
end
itemName = itemName or GetItemName(bagId, slotNum) or EMPTY_STRING
+ if itemName == "" then
+ if nil ~= IIfA.BagSlotInfo[bagId] and nil ~= IIfA.BagSlotInfo[bagId][slotNum] then
+ itemLink = IIfA.BagSlotInfo[bagId][slotNum]
+ itemName = GetItemLinkItemName(bagId, slotNum)
+ end
+ end
+
- IIfA:DebugOut(zo_strformat("EvalBagItem - <<1>>/<<2>> <<3>>", bagId, slotNum, itemName))
+ IIfA:DebugOut(zo_strformat("EvalBagItem bagId/slotId: <<1>>/<<2>> itemName: <<3>>", bagId, slotNum, itemName))
if itemName > EMPTY_STRING then
itemLink = itemLink or GetItemLink(bagId, slotNum, LINK_STYLE_BRACKETS)
@@ -428,7 +492,7 @@ function IIfA:ValidateItemCounts(bagID, slotNum, dbItem, itemKey, itemLinkOverri
else
itemLink = itemKey
end
- IIfA:DebugOut(zo_strformat("ValidateItemCounts: <<1>>x<<3>> in <<2>>", itemLink, bagID, slotNum))
+ IIfA:DebugOut(zo_strformat("ValidateItemCounts: <<1>> in bag <<2>>/<<3>>", itemLink, bagID, slotNum))
for locName, data in pairs(dbItem.locations) do
-- if data.bagID ~= nil then -- it's an item, not attribute
@@ -457,6 +521,8 @@ function IIfA:ValidateItemCounts(bagID, slotNum, dbItem, itemKey, itemLinkOverri
end
-- end
end
+
+ IIfA:UpdateBSI(bagID, slotNum)
end
@@ -468,35 +534,39 @@ function IIfA:CollectAll()
local BagList = IIfA:GetTrackedBags() -- 20.1. mana: Iterating over a list now
for bagId, tracked in ipairs(BagList) do
+ -- call with libAsync to avoid lags
+ task:Call(function()
- bagItems = GetBagSize(bagId)
- if(bagId == BAG_WORN)then --location for BAG_BACKPACK and BAG_WORN is the same so only reset once
- IIfA:ClearLocationData(IIfA.currentCharacterId)
- elseif(bagId == BAG_BANK) then -- do NOT add BAG_SUBSCRIBER_BANK here, it'll wipe whatever already got put into the bank on first hit
- IIfA:ClearLocationData(GetString(IIFA_BAG_BANK))
- elseif(bagId == BAG_VIRTUAL)then
- IIfA:ClearLocationData(GetString(IIFA_BAG_CRAFTBAG))
- elseif GetAPIVersion() >= 100022 then -- 20.1. mana: bag bag bag
- local collectibleId = GetCollectibleForHouseBankBag(bagId)
- if IsCollectibleUnlocked(collectibleId) then
- local name = GetCollectibleNickname(collectibleId) or GetCollectibleName(collectibleId)
- IIfA:ClearLocationData(name)
- end
- end
--- d(" BagItemCount=" .. bagItems)
- if bagId ~= BAG_VIRTUAL and tracked then
- for slotNum=0, bagItems, 1 do
- dbItem, itemKey = IIfA:EvalBagItem(bagId, slotNum)
+ bagItems = GetBagSize(bagId)
+ if(bagId == BAG_WORN)then --location for BAG_BACKPACK and BAG_WORN is the same so only reset once
+ IIfA:ClearLocationData(IIfA.currentCharacterId)
+ elseif(bagId == BAG_BANK) then -- do NOT add BAG_SUBSCRIBER_BANK here, it'll wipe whatever already got put into the bank on first hit
+ IIfA:ClearLocationData(GetString(IIFA_BAG_BANK))
+ elseif(bagId == BAG_VIRTUAL)then
+ IIfA:ClearLocationData(GetString(IIFA_BAG_CRAFTBAG))
+ elseif GetAPIVersion() >= 100022 then -- 20.1. mana: bag bag bag
+ local collectibleId = GetCollectibleForHouseBankBag(bagId)
+ if IsCollectibleUnlocked(collectibleId) then
+ local name = GetCollectibleNickname(collectibleId) or GetCollectibleName(collectibleId)
+ IIfA:ClearLocationData(name)
+ end
end
- else
- if HasCraftBagAccess() then
- slotNum = GetNextVirtualBagSlotId(nil)
- while slotNum ~= nil do
- IIfA:EvalBagItem(bagId, slotNum)
- slotNum = GetNextVirtualBagSlotId(slotNum)
+ -- d(" BagItemCount=" .. bagItems)
+ if bagId ~= BAG_VIRTUAL and tracked then
+ for slotNum=0, bagItems, 1 do
+ dbItem, itemKey = IIfA:EvalBagItem(bagId, slotNum)
+ end
+ else
+ if HasCraftBagAccess() then
+ slotNum = GetNextVirtualBagSlotId(nil)
+ while slotNum ~= nil do
+ IIfA:EvalBagItem(bagId, slotNum)
+ slotNum = GetNextVirtualBagSlotId(slotNum)
+ end
end
end
- end
+
+ end)
end
-- 6-3-17 AM - need to clear unowned items when deleting char/guildbank too
diff --git a/IIfA/IIfAEvents.lua b/IIfA/IIfAEvents.lua
index 948fb53..f53c4aa 100644
--- a/IIfA/IIfAEvents.lua
+++ b/IIfA/IIfAEvents.lua
@@ -16,18 +16,6 @@ local function IIfA_InventorySlotUpdate(...)
IIfA:InventorySlotUpdate(...)
end
-local function IIfA_CollectibleUpdate(eventCode, collectibleId, justUnlocked)
- if justUnlocked then return end
- if GetAPIVersion() < 100022 then return end
-
- local collectibleData = ZO_COLLECTIBLE_DATA_MANAGER:GetCollectibleDataById(collectibleId)
- if not collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_HOUSE_BANK) then return end
- local name = GetCollectibleName(collectibleId)
- local nickName = GetCollectibleNickname(collectibleId)
-
-
-end
-
local function IIfA_ScanHouse(eventCode, oldMode, newMode)
if newMode == "showing" or newMode == "shown" then return end
-- are we listening?
@@ -117,9 +105,6 @@ function IIfA:RegisterForEvents()
-- not helpful, no link at all on this callback
-- SHARED_INVENTORY:RegisterCallback("SlotRemoved", IIfA_EventDump)
-- SHARED_INVENTORY:RegisterCallback("SingleSlotInventoryUpdate", IIfA_EventDump)
-
- -- react to players possibly renaming their storage chests
- em:RegisterForEvent("IIFA_Collectible_Updated", EVENT_COLLECTIBLE_UPDATED, IIfA_CollectibleUpdate)
-- Events for data collection
diff --git a/IIfA/libs/LibAsync/LibAsync.lua b/IIfA/libs/LibAsync/LibAsync.lua
new file mode 100644
index 0000000..77100f0
--- /dev/null
+++ b/IIfA/libs/LibAsync/LibAsync.lua
@@ -0,0 +1,340 @@
+local MAJOR, MINOR = "LibAsync", 1.4
+local async, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+if not async then return end -- the same or newer version of this lib is already loaded into memory
+
+if async.Unload then
+ async:Unload()
+end
+
+local em = GetEventManager()
+local remove, min = table.remove, math.min
+
+local function RemoveCall(job, callstackIndex)
+ remove(job.callstack, callstackIndex)
+ job.lastCallIndex = min(job.lastCallIndex, #job.callstack)
+end
+
+local current, call
+local function safeCall() return call(current) end
+
+local function DoCallback(job, callstackIndex)
+ local success, shouldContinue = pcall(safeCall)
+ if success then
+ -- If the call returns true, the call wants to be called again
+ if not shouldContinue then RemoveCall(job, callstackIndex) end
+ else
+ -- shouldContinue is the value returned by error or assert
+ job.Error = shouldContinue
+ RemoveCall(job, callstackIndex)
+
+ call = job.onError
+ if call then
+ pcall(safeCall)
+ else
+ job:Suspend()
+ error(job.Error)
+ end
+ end
+end
+
+local jobs = async.jobs or { }
+async.jobs = jobs
+-- async.registered = { }
+
+local function DoJob(job)
+ current = job
+ local index = #job.callstack
+ call = job.callstack[index]
+ if call then
+ DoCallback(job, index)
+ else
+ assert(index == 0, "No call on non-empty stack?!")
+ jobs[job.name] = nil
+ call = job.finally
+ if call then pcall(safeCall) end
+ end
+ current, call = nil, nil
+end
+
+-- time we can spend until the next frame must be shown
+local frameTimeTarget = 13
+-- we allow a function to use 25% of the frame time before it gets critical
+local spendTimeDef = frameTimeTarget * 0.75
+local spendTimeDefNoHUD = spendTimeDef * 1.54
+local spendTime = spendTimeDef
+
+local debug = false
+
+local GetFrameTimeMilliseconds, GetGameTimeMilliseconds = GetFrameTimeMilliseconds, GetGameTimeMilliseconds
+local identifier = "ASYNCTASKS_JOBS"
+
+local function GetThreshold()
+ return(HUD_SCENE:IsShowing() or HUD_UI_SCENE:IsShowing()) and spendTimeDef or spendTimeDefNoHUD
+end
+
+local job = nil
+local function Scheduler()
+ local start = GetFrameTimeMilliseconds()
+ local runTime = start
+ if (GetGameTimeMilliseconds() - start) > spendTime then
+ spendTime = 750 / GetFramerate()
+ if debug then
+ df("initial gap: %ims. skip. new threshold: %ims", GetGameTimeMilliseconds() - start, spendTime)
+ end
+ return
+ end
+ if debug then
+ df("initial gap: %ims", GetGameTimeMilliseconds() - start)
+ end
+ local name
+ while (GetGameTimeMilliseconds() - start) <= spendTime do
+ name, job = next(jobs)
+ if job then
+ runTime = GetGameTimeMilliseconds()
+ DoJob(job)
+ else
+ -- Finished
+ em:UnregisterForUpdate(identifier)
+ spendTime = GetThreshold()
+ return
+ end
+ end
+ spendTime = GetThreshold()
+ if debug and job then
+ local now = GetGameTimeMilliseconds()
+ local freezeTime = now - start
+ if freezeTime >= 16 then
+ runTime = now - runTime
+ df("%s freeze. used %ims, resulting frametime %ims.", job.name, runTime, freezeTime)
+ end
+ end
+end
+
+function async:GetDebug()
+ return debug
+end
+
+function async:SetDebug(enabled)
+ debug = enabled
+end
+
+-- Class task
+
+local task = async.task or ZO_Object:Subclass()
+async.task = task
+
+-- Called from async:Create()
+function task:New(name)
+ local instance = ZO_Object.New(self)
+ instance.name = name or tostring(instance)
+ instance:Initialize()
+ return instance
+end
+
+function task:Initialize()
+ self.callstack = { }
+ self.lastCallIndex = 0
+ -- async.registered[#async.registered + 1] = self
+end
+
+-- Resume the execution context.
+function task:Resume()
+ jobs[self.name] = self
+ em:RegisterForUpdate(identifier, 0, Scheduler)
+ return self
+end
+
+-- Suspend the execution context and allow to resume anytime later.
+function task:Suspend()
+ jobs[self.name] = nil
+ return self
+end
+
+-- Interupt and fully stop the execution context. Can be called from outside to stop everything.
+function task:Cancel()
+ ZO_ClearNumericallyIndexedTable(self.callstack)
+ self.lastCallIndex = 0
+ if jobs[self.name] then
+ if not self.finally then
+ jobs[self.name] = nil
+ -- else run job with empty callstack to run finalizer
+ end
+ end
+ return self
+end
+
+do
+ -- Run the given FuncOfTask in your task context execution.
+ function task:Call(funcOfTask)
+ self.lastCallIndex = #self.callstack + 1
+ self.callstack[self.lastCallIndex] = funcOfTask
+ return self:Resume()
+ end
+
+ local insert = table.insert
+ -- Continue your task context execution with the given FuncOfTask after the previous as finished.
+ function task:Then(funcOfTask)
+ assert(self.lastCallIndex > 0 and self.lastCallIndex <= #self.callstack, "cap!")
+ insert(self.callstack, self.lastCallIndex, funcOfTask)
+ return self
+ end
+end
+
+-- Start an interruptible for-loop.
+function task:For(p1, p2, p3)
+ -- If called as a normal job, false will prevent it is kept in callstack doing an endless loop
+ self.callstack[#self.callstack + 1] = function() return false, p1, p2, p3 end
+ return self
+end
+
+do
+ local function ForConditionAlreadyFalse() end
+ local function ContinueForward(index, endIndex) return index <= endIndex end
+ local function ContinueBackward(index, endIndex) return index >= endIndex end
+
+ local function asyncForWithStep(self, func, index, endIndex, step)
+ step = step or 1
+ if step == 0 then error("step is zero") end
+
+ local ShouldContinue
+ if step > 0 then
+ if index > endIndex then return ForConditionAlreadyFalse end
+ ShouldContinue = ContinueForward
+ else
+ if index < endIndex then return ForConditionAlreadyFalse end
+ ShouldContinue = ContinueBackward
+ end
+ return function()
+ if func(index) ~= async.BREAK then
+ index = index + step
+ return ShouldContinue(index, endIndex)
+ end
+ end
+ end
+
+ local function asyncForPairs(self, func, iter, list, key)
+ return function()
+ local value
+ key, value = iter(list, key)
+ return key and func(key, value) ~= async.BREAK
+ end
+ end
+
+ -- Execute the async-for with the given step-function. The parameters of the step-function are those you would use in your for body.
+ function task:Do(func)
+ local callstackIndex = #self.callstack
+ local shouldBeFalse, p1, p2, p3 = self.callstack[callstackIndex]()
+ assert(shouldBeFalse == false and p1, "Do without For")
+ remove(self.callstack, callstackIndex)
+
+ local DoLoop = type(p1) == "number" and
+ asyncForWithStep(self, func, p1, p2, p3) or
+ asyncForPairs(self, func, p1, p2, p3)
+
+ if current or #self.callstack == 0 then return self:Call(DoLoop) else return self:Then(DoLoop) end
+ end
+end
+
+-- Suspend the execution of your task context for the given delay in milliseconds and then call the given FuncOfTask to continue.
+function task:Delay(delay, funcOfTask)
+ self:StopTimer()
+ if delay < 10 then return self:Call(funcOfTask) end
+ self:Suspend()
+ em:RegisterForUpdate(self.name, delay, function()
+ em:UnregisterForUpdate(self.name)
+ self:Call(funcOfTask)
+ end )
+ return self
+end
+
+-- Stop the delay created by task:Delay or task:Interval.
+function task:StopTimer()
+ em:UnregisterForUpdate(self.name)
+ return self
+end
+
+-- Set a FuncOfTask as a final handler. If you call Called if something went wrong in your context.
+function task:Finally(funcOfTask)
+ self.finally = funcOfTask
+ return self
+end
+
+-- Set a FuncOfTask as an error handler. Called if something went wrong in your context.
+function task:OnError(funcOfTask)
+ self.onError = funcOfTask
+ return self
+end
+
+do
+ -- Thanks to: https://de.wikipedia.org/wiki/Quicksort
+
+ local function simpleCompare(a, b) return a < b end
+ local function sort(task, array, compare)
+ local function quicksort(left, right)
+ if left >= right then return end
+
+ -- partition
+ local i, j, pivot = left, right - 1, array[right]
+
+ task:Call( function()
+ while i < right and compare(array[i], pivot) do i = i + 1 end
+ while j > left and not compare(array[j], pivot) do j = j - 1 end
+ if i < j then
+ array[i], array[j] = array[j], array[i]
+ -- repeatly call this function until i >= j
+ return true
+ end
+ end )
+ task:Then( function()
+ if compare(pivot, array[i]) then array[i], array[right] = array[right], array[i] end
+ quicksort(left, i - 1)
+ quicksort(i + 1, right)
+ end )
+ end
+ quicksort(1, #array)
+ end
+
+ -- This sort function works like table.sort(). The compare function is optional.
+ function task:Sort(array, compare)
+ local sortJob = function(task) sort(task, array, compare or simpleCompare) end
+ if current or #self.callstack == 0 then return self:Call(sortJob) else return self:Then(sortJob) end
+ end
+end
+
+-- Class async
+
+-- Get the current context, if you are within a FuncOfTask or nil.
+function async:GetCurrent()
+ return current
+end
+
+-- Create an interruptible task context.
+function async:Create(name)
+ return task:New(name)
+end
+
+do
+ local Default = task:New("*Default Task*")
+ function Default:Cancel() error("Not allowed on default task. Use your_lib_var:Create(optional_name) for an interruptible task context.") end
+ Default.Finally = Default.Cancel
+ Default.OnError = Default.Cancel
+
+ -- Start a non-interruptible task or start a nested call in the current context.
+ function async:Call(funcOfTask)
+ -- if async:Call is called from within a task callback (the moment where GetCurrent() is not nil) use it for nested calls
+ return(async:GetCurrent() or Default):Call(funcOfTask)
+ end
+ -- Start a non-interruptible for-loop or start a nested for-loop in the current context.
+ function async:For(p1, p2, p3)
+ return(self:GetCurrent() or Default):For(p1, p2, p3)
+ end
+
+ -- Start a non-interruptible sort or start a nested sort in the current context.
+ function async:Sort(array, compare)
+ return(self:GetCurrent() or Default):Sort(array, compare)
+ end
+end
+
+-- async.BREAK is the new 'break' for breaking loops. As Lua would not allowed the keyword 'break' in that context.
+-- To break a for-loop, return async.BREAK
+async.BREAK = true