stick-the-quick/util/TweenChannel.gd

89 lines
3.2 KiB
GDScript

class_name TweenChannel extends RefCounted
## Arbitrates between concurrent tweens, e.g. of the same property.
## Emitted when a tween finishes or is killed.
signal terminated(what: Tween)
## How to arbitrate between a tween request and an already running tween.
enum ArbitrationMode {
YIELD, ## Request yields until there is no longer any current tween.
REPLACE, ## Rather than request yielding, current tween is killed.
IGNORE ## Rather than request yielding, request is denied (returns null).
}
## How to arbitrate between a tween request and an already running tween.
##
## Takes effect only for new calls to self.create_tween,
## not in-progress calls. That is to say, if self.create_tween
## has already been called at a time when arbitration_mode was YIELD,
## and it changes while the call is yielding, then the in-progress call
## still will not return until its turn, as though arbitration_mode
## were still YIELD.
var arbitration_mode: ArbitrationMode
## Tween currently running if any, else null.
var current_tween: Tween = null
## Initializer. Sets arbitration mode.
func _init(
p_arbitration_mode: ArbitrationMode = ArbitrationMode.YIELD
) -> void:
arbitration_mode = p_arbitration_mode
## Creates a TweenChannel which arbitrates by yielding.
static func make_yielding() -> TweenChannel:
return new(ArbitrationMode.YIELD)
## Creates a TweenChannel which arbitrates by replacing.
static func make_replacing() -> TweenChannel:
return new(ArbitrationMode.REPLACE)
## Creates a TweenChannel which arbitrates by ignoring.
static func make_ignoring() -> TweenChannel:
return new(ArbitrationMode.IGNORE)
## Returns target.create_tween() if and when arbitration_mode allows.
##
## "If:" if arbitration_mode is IGNORE and there is a current tween,
## then null is returned.
## "When:" if arbitration_mode is YIELD and there is a current tween,
## then this function yields until there is not.
## (If arbitration_mode is REPLACE and there is a current tween,
## then this function returns target.create_tween() immediately,
## and the preexisting current tween is killed.)
func create_tween(target: Variant) -> Tween:
assert(target is Node or target is SceneTree)
match arbitration_mode:
ArbitrationMode.YIELD:
while current_tween:
await terminated
ArbitrationMode.REPLACE:
if current_tween and current_tween.is_valid():
current_tween.kill()
ArbitrationMode.IGNORE:
if current_tween:
return null
current_tween = target.create_tween()
_cleanup_later(current_tween)
return current_tween
## Yields until the given tween has terminated.
##
## The given tween must have been created with self.create_tween.
func sync(what: Tween) -> void:
if what and what.is_valid():
var last_terminated: Tween = null
while last_terminated != what:
last_terminated = await terminated
## Yields until the given tween is invalid and then cleans up bookkeeping.
##
## Bookkeeping cleanup entails the following:
## - if the given tween is the current tween, clears the current tween;
## - terminated is emitted with the given tween as the argument.
func _cleanup_later(what: Tween) -> void:
while what.is_valid():
await Wait.tick()
if current_tween == what:
current_tween = null
terminated.emit(what)