您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
298 行
8.6 KiB
298 行
8.6 KiB
using System;
|
|
using System.Text;
|
|
using RSG;
|
|
using RSG.Promises;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.ui;
|
|
|
|
namespace Unity.UIWidgets.scheduler {
|
|
public delegate void TickerCallback(TimeSpan elapsed);
|
|
|
|
public interface TickerProvider {
|
|
Ticker createTicker(TickerCallback onTick);
|
|
}
|
|
|
|
public class Ticker {
|
|
public Ticker(TickerCallback onTick, Func<string> debugLabel = null) {
|
|
D.assert(() => {
|
|
_debugCreationStack = "skipped, use StackTraceUtility.ExtractStackTrace() if you need it"; // StackTraceUtility.ExtractStackTrace();
|
|
return true;
|
|
});
|
|
_onTick = onTick;
|
|
this.debugLabel = debugLabel;
|
|
}
|
|
|
|
TickerFutureImpl _future;
|
|
|
|
public bool muted {
|
|
get { return _muted; }
|
|
set {
|
|
if (value == _muted) {
|
|
return;
|
|
}
|
|
|
|
_muted = value;
|
|
if (value) {
|
|
unscheduleTick();
|
|
}
|
|
else if (shouldScheduleTick) {
|
|
scheduleTick();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _muted = false;
|
|
|
|
public bool isTicking {
|
|
get {
|
|
if (_future == null) {
|
|
return false;
|
|
}
|
|
|
|
if (muted) {
|
|
return false;
|
|
}
|
|
|
|
if (SchedulerBinding.instance.framesEnabled) {
|
|
return true;
|
|
}
|
|
|
|
if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool isActive {
|
|
get { return _future != null; }
|
|
}
|
|
|
|
TimeSpan? _startTime;
|
|
|
|
public TickerFuture start() {
|
|
D.assert(() => {
|
|
if (isActive) {
|
|
throw new UIWidgetsError(
|
|
"A ticker that is already active cannot be started again without first stopping it.\n" +
|
|
"The affected ticker was: " + toString(debugIncludeStack: true));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
D.assert(_startTime == null);
|
|
_future = new TickerFutureImpl();
|
|
if (shouldScheduleTick) {
|
|
scheduleTick();
|
|
}
|
|
|
|
if (SchedulerBinding.instance.schedulerPhase > SchedulerPhase.idle &&
|
|
SchedulerBinding.instance.schedulerPhase < SchedulerPhase.postFrameCallbacks) {
|
|
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
|
|
}
|
|
|
|
return _future;
|
|
}
|
|
|
|
public void stop(bool canceled = false) {
|
|
if (!isActive) {
|
|
return;
|
|
}
|
|
|
|
var localFuture = _future;
|
|
_future = null;
|
|
_startTime = null;
|
|
D.assert(!isActive);
|
|
|
|
unscheduleTick();
|
|
if (canceled) {
|
|
localFuture._cancel(this);
|
|
}
|
|
else {
|
|
localFuture._complete();
|
|
}
|
|
}
|
|
|
|
readonly TickerCallback _onTick;
|
|
|
|
int? _animationId;
|
|
|
|
protected bool scheduled {
|
|
get { return _animationId != null; }
|
|
}
|
|
|
|
protected bool shouldScheduleTick {
|
|
get { return !muted && isActive && !scheduled; }
|
|
}
|
|
|
|
void _tick(TimeSpan timeStamp) {
|
|
D.assert(isTicking);
|
|
D.assert(scheduled);
|
|
_animationId = null;
|
|
|
|
_startTime = _startTime ?? timeStamp;
|
|
|
|
_onTick(timeStamp - _startTime.Value);
|
|
|
|
if (shouldScheduleTick) {
|
|
scheduleTick(rescheduling: true);
|
|
}
|
|
}
|
|
|
|
protected void scheduleTick(bool rescheduling = false) {
|
|
D.assert(!scheduled);
|
|
D.assert(shouldScheduleTick);
|
|
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
|
|
}
|
|
|
|
protected void unscheduleTick() {
|
|
if (scheduled) {
|
|
SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId.Value);
|
|
_animationId = null;
|
|
}
|
|
|
|
D.assert(!shouldScheduleTick);
|
|
}
|
|
|
|
public void absorbTicker(Ticker originalTicker) {
|
|
D.assert(!isActive);
|
|
D.assert(_future == null);
|
|
D.assert(_startTime == null);
|
|
D.assert(_animationId == null);
|
|
D.assert((originalTicker._future == null) == (originalTicker._startTime == null),
|
|
() => "Cannot absorb Ticker after it has been disposed.");
|
|
if (originalTicker._future != null) {
|
|
_future = originalTicker._future;
|
|
_startTime = originalTicker._startTime;
|
|
if (shouldScheduleTick) {
|
|
scheduleTick();
|
|
}
|
|
|
|
originalTicker._future = null;
|
|
originalTicker.unscheduleTick();
|
|
}
|
|
|
|
originalTicker.dispose();
|
|
}
|
|
|
|
public virtual void dispose() {
|
|
if (_future != null) {
|
|
var localFuture = _future;
|
|
_future = null;
|
|
D.assert(!isActive);
|
|
unscheduleTick();
|
|
localFuture._cancel(this);
|
|
}
|
|
|
|
D.assert(() => {
|
|
_startTime = default(TimeSpan);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
internal readonly Func<string> debugLabel;
|
|
|
|
string _debugCreationStack;
|
|
|
|
public override string ToString() {
|
|
return toString(debugIncludeStack: false);
|
|
}
|
|
|
|
public string toString(bool debugIncludeStack = false) {
|
|
var buffer = new StringBuilder();
|
|
buffer.Append(GetType() + "(");
|
|
D.assert(() => {
|
|
if (debugLabel != null) {
|
|
buffer.Append(debugLabel());
|
|
}
|
|
return true;
|
|
});
|
|
buffer.Append(')');
|
|
D.assert(() => {
|
|
if (debugIncludeStack) {
|
|
buffer.AppendLine();
|
|
buffer.AppendLine("The stack trace when the " + GetType() + " was actually created was:");
|
|
UIWidgetsError.defaultStackFilter(_debugCreationStack.TrimEnd().Split('\n'))
|
|
.Each(line => buffer.AppendLine(line));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return buffer.ToString();
|
|
}
|
|
}
|
|
|
|
public interface TickerFuture : IPromise {
|
|
void whenCompleteOrCancel(VoidCallback callback);
|
|
|
|
IPromise orCancel { get; }
|
|
}
|
|
|
|
public class TickerFutureImpl : Promise, TickerFuture {
|
|
public static TickerFuture complete() {
|
|
var result = new TickerFutureImpl();
|
|
result._complete();
|
|
return result;
|
|
}
|
|
|
|
Promise _secondaryCompleter;
|
|
bool? _completed;
|
|
|
|
internal void _complete() {
|
|
D.assert(_completed == null);
|
|
_completed = true;
|
|
Resolve();
|
|
if (_secondaryCompleter != null) {
|
|
_secondaryCompleter.Resolve();
|
|
}
|
|
}
|
|
|
|
internal void _cancel(Ticker ticker) {
|
|
D.assert(_completed == null);
|
|
_completed = false;
|
|
if (_secondaryCompleter != null) {
|
|
_secondaryCompleter.Reject(new TickerCanceled(ticker));
|
|
}
|
|
}
|
|
|
|
public void whenCompleteOrCancel(VoidCallback callback) {
|
|
orCancel.Then(() => callback(), ex => callback());
|
|
}
|
|
|
|
public IPromise orCancel {
|
|
get {
|
|
if (_secondaryCompleter == null) {
|
|
_secondaryCompleter = new Promise();
|
|
if (_completed != null) {
|
|
if (_completed.Value) {
|
|
_secondaryCompleter.Resolve();
|
|
}
|
|
else {
|
|
_secondaryCompleter.Reject(new TickerCanceled());
|
|
}
|
|
}
|
|
}
|
|
|
|
return _secondaryCompleter;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TickerCanceled : Exception {
|
|
public TickerCanceled(Ticker ticker = null) {
|
|
this.ticker = ticker;
|
|
}
|
|
|
|
public readonly Ticker ticker;
|
|
|
|
public override string ToString() {
|
|
if (ticker != null) {
|
|
return "This ticker was canceled: " + ticker;
|
|
}
|
|
|
|
return "The ticker was canceled before the \"orCancel\" property was first used.";
|
|
}
|
|
}
|
|
}
|