dphfox / Fusion

A modern reactive UI library, built specifically for Roblox and Luau.
https://elttob.uk/Fusion/
MIT License
606 stars 102 forks source link

Timers #12

Open dphfox opened 3 years ago

dphfox commented 3 years ago

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 commented 3 years ago

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

BPilot253 commented 3 years ago

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

dphfox commented 3 years ago

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

BPilot253 commented 3 years ago

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

Ukendio commented 3 years ago

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.

dphfox commented 3 years ago

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:

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:

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 🙂

Ukendio commented 3 years ago

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
dphfox commented 3 years ago

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.

Ukendio commented 3 years ago

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

dphfox commented 2 years ago

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 commented 2 years ago

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.

frqstbite commented 6 months ago

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

dphfox commented 6 months ago

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