- Joined
- Sep 26, 2009
- Messages
- 9,534
Thank you, I had mixed up the terminology. I have fixed the placement of the local declaration and updated the functionality to take advantage of Hook 3.1's ability to modify parameters.
Thanks, I have just updated this, Fast Triggers and even Hook (so that Hook uses the new LinkedList insert method).Needs a small update for the new Hook version.
_PRIORITY = 1 currently results in an after hook.
if AddHook then -- https://www.hiveworkshop.com/threads/hook.339153
--Precise Wait v1.4.0.0 beta
--This changes the default functionality of TriggerSleepAction, PolledWait
--TriggerAddAction and SyncSelections. If there are any natives that force
--a thread to yield that won't work with Lua coroutines, please let me know.
local _ACTION_PRIORITY = 1 --Specify the hook priority for hooking TriggerAddAction (higher numbers run earlier in the sequence).
local _WAIT_PRIORITY = -2 --The hook priority for TriggerSleepAction/PolledWait
local function wait(duration)
local thread = coroutine.running()
if thread then
local t = CreateTimer()
TimerStart(t, duration, false, function()
DestroyTimer(t)
coroutine.resume(thread)
end)
coroutine.yield(thread)
end
end
AddHook("PolledWait", wait, _WAIT_PRIORITY)
AddHook("TriggerSleepAction", wait, _WAIT_PRIORITY)
local oldSync
oldSync = AddHook("SyncSelections",
function()
local thread = coroutine.running()
if thread then
function SyncSelectionsHelper()
oldSync()
coroutine.resume(thread)
end
ExecuteFunc("SyncSelectionsHelper")
coroutine.yield(thread)
end
end)
local oldAdd
local runningIndex
oldAdd = AddHook("TriggerAddAction", function(trig, func)
if GlobalRemap then
if not runningIndex then
GlobalRemap("udg_WaitIndex", function() return runningIndex end)
runningIndex = 0
end
oldAdd(trig, function()
runningIndex = runningIndex + 1
coroutine.wrap(func)()
runningIndex = runningIndex - 1
end)
else
oldAdd(trig, coroutine.wrap(func))
end
end, _ACTION_PRIORITY)
end --End of library PolledWait
This is added in this beta version. Sorry it took so long to follow through on this.Would you add "WaitIndex" global variable that could be used to save data for each next wait execution? Like we did it with timers "GetHandeId(timer)". Easier and may be cheaper than overriding all the thread functions such as "GetTrigger..."
This error can occur if you are trying to use waits outside of a triggeraction or ExecuteFunc instance. These are special kinds of functions which are yieldable (but don't have coroutines wrapped around them, so I have to create them myself). BoolExpr and Code callback functions such as Filter, Condition, TimerStart, ForGroup cannot have waits in them, even with this resource.I can't get this to work. Code after the Polled Wait just doesn't execute at all.
Example:
Adding this function to any trigger and execute the trigger will only show the first message.Lua:function testPolledWait() print("Before Polled Wait") PolledWait(2.) print("After Polled Wait") end
Reason seems to be that blizzard coroutines can't be yielded (anymore?). You can even ask the running coroutine, if it is yieldable and it answers "false". If you still try, it wil raise an error:
Lua:--do this in any function: c = coroutine.running() print(coroutine.isyieldable(c)) --> will print false coroutine.yield(c) --doesn't work and raises the error "attempt to yield from blizzard coroutine"
local oldAdd, init
oldAdd = AddHook("TriggerAddAction", function(trig, func)
if GlobalRemap and not init then
init = true
GlobalRemap("udg_WaitIndex", function() return coroutine.running() end)
end
oldAdd(trig, coroutine.wrap(func))
end, _ACTION_PRIORITY)
do
local t = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddAction(t, function ()
print("OnCast")
end)
end
oldAdd(trig, function()
coroutine.resume(coroutine.create(function()
func()
end))
end)
This problem happened to me (I case you create the spells in the way you are showing), the reason is because I created the trigger and register the event in the main code and not within a OnTrigInit block, they told me something about the garbage collector collecting the trigger and/or the event.This part seems to break basic spell events:
This prints OnCast only once. Further spell events don't work.Lua:do local t = CreateTrigger() TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) TriggerAddAction(t, function () print("OnCast") end) end
Isn't this the result of creating handles on lua root? it's just getting garbage collected.This part seems to break basic spell events:
This prints OnCast only once. Further spell events don't work.Lua:do local t = CreateTrigger() TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) TriggerAddAction(t, function () print("OnCast") end) end
Not in this scenario. The root cause was my incorrect assumption that coroutine.wrap would perpetually create a new coroutine when that function is called (when in fact it dies after one use, making it incredibly useless).Isn't this the result of creating handles on lua root? it's just getting garbage collected.
Can you explain why there are problems with cleanup? Does coroutine.running() return something that needs to be cleaned up?"WaitIndex" which Global Variable Remapper turns into returning the value of "coroutine.running()" in order to ensure the index is unique to that set of triggeractions. The cleanup is tricky with this without setting values to nil, so I recommend using GUI Repair Kit to make sure your arrays (tables) get cleaned up when the thread execution has ended.
The Lua garbage collector will only pick up items that no longer have references pointing to them, so when you are using the thread object as a reference (which is what is returned by coroutine.running()) you need to make sure that that key gets erased at some point. The way to automate this is by using __mode = "k" within a metatable, which is handled by [Lua] - GUI Repair Collection.Can you explain why there are problems with cleanup? Does coroutine.running() return something that needs to be cleaned up?
Correct. I could alternatively do some kind of integer-based struct indexing that is used to represent the WaitIndex object, but it would need an incremental timer to check for when the status of the coroutine becomes "dead". WarCraft 3 does not allow hooking of the garbage collector API, so in this sense I could not rely on the __mode="k" metatable to clean that struct integer.So it's just a problem if you use the value returned by WaitIndex as array/table index?
Approve me too senpaiYeah, no I think the current strategy is best. Approved.
If you are coding within Lua, you don't need to do this sort of thing, as you can use native timers and their callback functions will inherit the locals from the parent function as upvalues.Is there any way to use this without replacing the original functions? I'd like to just call this function instead.
I'm actually just starting to learn Lua, so I was unaware of all the other alternatives. I'll search up how to do those, thanks.If you are coding within Lua, you don't need to do this sort of thing, as you can use native timers and their callback functions will inherit the locals from the parent function as upvalues.
If you prefer to use fewer lines of code to achieve the same results, I can recommend using Timed.call, which hides the timer API and allows you to focus on getting to the next block of your code.
If you really just want to yield, you need to make sure you are within a yieldable coroutine and can just call PolledWait from there.
This resource is one of the main reasons why Lua is so much better for GUI users: because you can change the behavior of native functions (and even GUI variables). This sort of thing is impossible in GUI when the map is configured for JASS.This looks very good and useful.
Though I heard tha Lua can't be used with Jass, since its the map settings and all.
Is it possible to use this with GUI+JASS?
if p == GetLocalPlayer() then
would cause a desync, is there a way to do a similar system that does this in those blocks?If the timers were recycled, it wouldn't desync. But I think a timer recycling system should be its own separate resource.Seeing this system I'm noticing that using it in aif p == GetLocalPlayer() then
would cause a desync, is there a way to do a similar system that does this in those blocks?
But theIf the timers were recycled, it wouldn't desync. But I think a timer recycling system should be its own separate resource.
TimerStart
function doesn't cause a desync if is called in the if p == GetLocalPlayer() then
block?But theTimerStart
function doesn't cause a desync if is called in theif p == GetLocalPlayer() then
block?
function Hook:ExecuteFunc(funcName)
co = coroutine.create(_G[funcName])
coroutine.resume(co)
end
-- Some function that is running from none Trigger context.
-- For example running as a trigger condition or in a timer
function SomeFunc(arg1, ...)
print("before" .. tostring(arg1))
TriggerSleepAction(1)
print("after")
end
-- Need to write this function and call it instead of the internal "SomeFunc"
function WrapperFunc(arg1, ...)
co = coroutine.create(SomeFunc)
coroutine.resume(co, arg1, ...)
end
---@param duration number
local function wait(duration)
local thread = coroutine.running()
if thread and coroutine.isyieldable() then
TimerQueue:callDelayed(duration, function()
coroutine.resume(thread)
end)
coroutine.yield()
elseif not thread then
softErrorHandler('Wait', 'was called from an invalid thread.')
else
softErrorHandler('Wait', 'called from a none yieldable coroutine.')
end
end