Fusion icon indicating copy to clipboard operation
Fusion copied to clipboard

Timers

Open dphfox opened this issue 3 years ago • 13 comments

A useful addition to Fusion may be an object that acts like a stopwatch - useful for driving animations with especially.

One possible API (using methods to start and pause):

local timer = Timer()

print(timer:get()) -- 0
timer:start()
wait(5)
timer:pause()
print(timer:get()) -- about 5

Another possible API (using a state object to pause and resume)

local paused = State(true)
local timer = Timer(paused)

print(timer:get()) -- 0
paused:set(false)
wait(5)
paused:set(true)
print(timer:get()) -- about 5

There could possibly also be options for setting a 'max duration', looping, variable speed, and setting the current timer position.

dphfox avatar Aug 11 '21 17:08 dphfox

Could also be useful in conjunction with timelines: https://github.com/Elttob/Fusion/issues/11

dphfox avatar Aug 11 '21 17:08 dphfox

So basically this would act like tweening a value? (like the example you gave in the Timeline issue)

BPilot253 avatar Aug 11 '21 17:08 BPilot253

It's not really the same as a tween - a tween approaches a value over time, whereas a timer constantly ticks upward 🙂

dphfox avatar Aug 11 '21 17:08 dphfox

It's not really the same as a tween - a tween approaches a value over time, whereas a timer constantly ticks upward 🙂

Well yeah I meant it in the same use case as the currentTime in the Timeline component. Either way, it's a great addition once again, it has a lot of different use cases

BPilot253 avatar Aug 11 '21 17:08 BPilot253

I am opposed to polluting the Fusion namespace (export table) with a bunch of utility functions that is rather trivial to implement yourself and doesn't always have a significant use-case like Spring and Tween.

Ukendio avatar Aug 24 '21 19:08 Ukendio

I am opposed to polluting the Fusion namespace (export table) with a bunch of utility functions that is rather trivial to implement yourself and doesn't always have a significant use-case like Spring and Tween.

While I generally agree that we don't need an API for everything & the kitchen sink, I disagree with your specific argument here:

  • Springs are not trivial to implement - to implement them either requires a solid knowledge of the involved calculus, or the importing of a third party library
  • They encourage a declarative mode of thinking - sure, you can tween stuff with TweenService, but that breaks the declarative mental model because it's an imperative API. Keeping things in terms of state objects is useful because it keeps implementation details out of business logic and manages data synchronisation reliably.
  • These are APIs with significant demand and use cases - a lot of Fusion users are specifically interested in its animation capabilities. Aside from the issue you raise, these APIs have been met with near-universal approval.

Generally, when I propose a new API or evaluate a suggestion for an API, I look to these points to see whether it's worth adding:

  • Does it allow the expression of imperative code in a more declarative style?
  • Does it help avoid suboptimal code and provide useful optimisation by default, such as caching or lazy evaluation?
  • Does it allow the library to handle certain parts of code that are very easy to mess up and introduce bugs into?
  • Does it make code that isn't currently self evident easier to read?

These aren't strict points, but rather points for consideration. This is roughly the thought process I follow to try and disambiguate between 'kitchen sink' APIs and stuff that would generally be useful for developers to leverage.

Personally, I think that these APIs are important enough to warrant being in the core Fusion library. One of Fusion's self-proclaimed upsides is that there's a standard implementation for the most common primitives and tasks to aid with cross-project consistency and compatibility, and to make the DX much more straightforward, especially for basic or lightweight projects.

I appreciate your concerns though - I open these feature requests as issues precisely so I can gather this kind of feedback, rather than just blindly implementing stuff 🙂

dphfox avatar Aug 26 '21 07:08 dphfox

While I generally agree that we don't need an API for everything & the kitchen sink, I disagree with your specific argument here:

  • Springs are not trivial to implement - to implement them either requires a solid knowledge of the involved calculus, or the importing of a third party library
  • They encourage a declarative mode of thinking - sure, you can tween stuff with TweenService, but that breaks the declarative mental model because it's an imperative API. Keeping things in terms of state objects is useful because it keeps implementation details out of business logic and manages data synchronisation reliably.
  • These are APIs with significant demand and use cases - a lot of Fusion users are specifically interested in its animation capabilities. Aside from the issue you raise, these APIs have been met with near-universal approval.

Just making sure you understand me, I am saying that I don't think Timer is as useful AS Spring and Tween. These two needs to be nested in the project due to how they work, something like a timer object can be created without that requirement and still offer the same declarative mode of thinking. The first API example can be designed completely without Fusion as a dependency.

If it were to be implemented I would need to understand why a timer class needs to be counted as a dependency.

For example this is a simple implementation, with no obvious caveat afaik.

local TimerClass = {}
TimerClass.__index = TimerClass

function TimerClass:get() 
	return self.time_left
end

function TimerClass:pause() 
	self.time_connection:Disconnect()
end

local function Timer(interval: number) 
	local self = setmetatable({ time_left = interval }, TimerClass)
	self.time_connection = game:GetService("RunService").Heartbeat:Connect(function(dt) 
		self.time_left -= dt
	end)

	return self
end

local timer = Timer(5)
timer:get() -- 5

wait(2)

timer:pause()
timer:get() -- 3

Ukendio avatar Aug 26 '21 09:08 Ukendio

Just making sure you understand me, I am saying that I don't think Timer is as useful AS Spring and Tween.

I see Timer being broadly useful for defining all sorts of declarative animations that are driven by time (e.g. loading spinners/throbbers, pulse effects, keyframed animations), and so I think that even though it's relatively simple to implement, it'd be beneficial to have one well-designed, consistent inbox solution for it.

dphfox avatar Aug 29 '21 00:08 dphfox

That is a good point! Would you possibly have a draft, or a rough design of its implementation?

Ukendio avatar Aug 29 '21 00:08 Ukendio

Interesting thought - how will this garbage collect?

Presumably, a Timer will update on every render step. This could very easily interfere with #133. Furthermore, this implies a RunService connection is being maintained, which could easily introduce a strong, long-lived reference. We need to be careful about this.

dphfox avatar Feb 08 '22 20:02 dphfox

Something interesting to consider - while for most applications having a continuously incrementing timer is desirable, it might also be worth investigating frame-counting timers too. This could be useful for applications where a fixed time step is used for consistent or deterministic value reproduction.

dphfox avatar Aug 19 '22 11:08 dphfox

It's not really the same as a tween - a tween approaches a value over time, whereas a timer constantly ticks upward 🙂

how is this meaningfully different from Tween chained into a Math.floor()? does it just tick up forever? what are the implications of that memory-wise

frqstbite avatar May 13 '24 19:05 frqstbite

Yes, timers tick up forever. They are not floored, they use real numbers of seconds. This has no memory implications.

dphfox avatar May 13 '24 22:05 dphfox