Making a Tween function, need help with coroutines

**So I’m trying to make a Tween function that allows me to add transitions to derma elements or HUDs. Here is the source code:
**


local twn = {}
twn.position = 0
twn.duration = 3 -- time in seconds that the Tween transtion will last

function twn:Equation(pos)
	return pos
end

function twn:Tween(func)
	self.position=0
	local thread = coroutine.create(function()
		while self.position < 1 do
			local DeltaTime = FrameTime()
			self.position=self.position+(DeltaTime/self.duration)
			func(self:Equation(self.position))
			coroutine.wait(DeltaTime)
		end
		func(1)
	end)
	coroutine.resume(thread)
end


The above code, initially, does nothing. You have to execute this next piece of code to make the Tween start.


twn:Tween(function(ratio)
	x=Lerp(ratio,10,500)
end)

**What SHOULD happen, is the ‘Tween’ function will execute, the position variable with be set to 0, and it will run a coroutine which should run asynchronous from the rest of the code, in the coroutine it will go through a loop which exits when position equals 1, while it runs through the while loop it is adding the FrameTime divided by the duration variable to the position variable to properly increment it, when it executes the ‘func’ argument function that has a ‘ratio’ parameter that can be used to set the value of a variable as seen in the second piece of code, and then the coroutine SHOULD pause after this at ‘coroutine.wait(DeltaTime)’ for the DeltaTime which is FrameTime(), and after that it will resume and repeat until the loop is complete and exits out and runs the ‘func’ argument function one last time and passes a value of 1 to the ‘ratio’ argument.

It’s kinda of a strange way to do it, but it’s all compacted into a function and makes Tweening simple.

For example, if we made a derma frame and wanted it to transition onto the screen. We could do:**


local frame = vgui.Create("DFrame")
frame:SetSize(500,500)
frame:SetPos((ScrW()/2)-(500/2),-500)

twn:Tween(function(ratio)
	frame:SetPos( (ScrW()/2)-(500/2) , Lerp(ratio,-500,100) )
end)

**in case that you don’t know what ‘Lerp’ Does, Lerp stands for Linear Interpolation, and it returns the number that is ‘ratio’ (a number between 0 and 1) between the second and third arguments (-500 and 100)

so**


print(Lerp(0.25,-500,100)) = -350
print(Lerp(0.5,-500,100)) = -200
print(Lerp(0.75,-500,100)) = -50

**SO… The problem is that ‘coroutine.wait()’ doesn’t seem to be working. When I test the code, it pauses at that code and the thread never resumes. I know that the .wait function is not a part of the original coroutine library, and I’ve looked around for other people that use the coroutine library in Garry’s mod, nothing turned up. So I wanted to see if anyone here knew anything about Tweening in Garry’s mod or if anyone knew if I was using the coroutine library wrong, or if the library won’t work to begin with…

Thanks,**

[lua]
– lua/includes/extensions/coroutine.lua: 15-26
function coroutine.wait( seconds )
local endtime = CurTime() + seconds
while ( true ) do
if ( endtime < CurTime() ) then return end
coroutine.yield()
end
end
[/lua]

coroutine.wait is a garryfunction and therefore misleadingly named and broken for most purposes.

Instead of running an accumulator, you can calculate your position parameter instantaneously with:
[lua]
local currentTime = SysTime()
local t = (currentTime - tweenStartTime) / tweenDuration
[/lua]
but you will probably want to clamp it:
[lua]
local t = math.max(0, math.min(1, (currentTime - tweenStartTime) / tweenDuration))
[/lua]
math.max and math.min are branchless, get jit-compiled well and are cross-lua platform, while math.Clamp has none of these properties (you can tell by the naming convention)

You could run this off the Panel:Think hook and discard finished tweens (currentTime > tweenStartTime + tweenDuration)

also some bugs to avoid(?):

  • what happens if your UI controls get closed / removed while a tween is in progress?
  • what happens if a tween starts while one is already in progress?

@!cake Thanks for your input and informing me that coroutines are complete caca. I did though, discover that the timer library actually works far better at creating asynchronous threads than the coroutine library.

this is what I did:


function equation(x)
	return math.sqrt(x)
end

local tweensOpen = 0

function Tween(duration,func)
	local x=0
	local timerName = "TweenThread"..tostring(tweensOpen)
	tweensOpen=tweensOpen+1
	timer.Create(timerName,0,0,function()
		x=x+(FrameTime()/duration)
		if x>=1 then
			func(1)
			tweensOpen=tweensOpen-1
			timer.Remove(timerName)
		else
			func(equation(x))
		end
	end)
end


the above code is just a basic “build off of” function that can be made more complicated, but serves it’s job very well.
The ‘equation(x)’ function acts as an algebraic function that requires an independent variable (x), and will return the dependent variable (y)

at this link you will see some algebraic functions that intersect both (0,0) and (1,1) and with that being said, they can be used in ‘equation(x)’ function to change the Tweening style.

You can also see some Tweening functions visualized with graphs here

the ‘tweensOpen’ variable is used so that the tween function can be used multiple times at once. I haven’t tested it completely, but I feel that It might go wrong somewhere along the line.

If anyone knows how to use ‘timer.Simple()’ function and have it loop like what ‘timer.Create()’ does when you set the repetitions parameter to 0 which makes it loop infinitely, hint, you can’t really use while loops because it freezes the game :confused:

Keep in mind that timer.Create is not actually asynchronous. Whole Lua environment AFAIK runs on a single thread (and that’s a good thing!). That’s why your while loop freezes the game.

Cake’s approach is the best for this purpose. By using timers you’re just delegating the computation to another part of the event loop, which is also more expensive because of anonymous functions (unless your func() is really expensive).