您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
385 行
14 KiB
385 行
14 KiB
/*
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using RSG.Promises;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.scheduler2;
|
|
using Unity.UIWidgets.ui;
|
|
using Debug = UnityEngine.Debug;
|
|
using FrameCallback = Unity.UIWidgets.ui.FrameCallback;
|
|
|
|
namespace Unity.UIWidgets.scheduler {
|
|
class _FrameCallbackEntry {
|
|
internal _FrameCallbackEntry(FrameCallback callback, bool rescheduling = false) {
|
|
this.callback = callback;
|
|
|
|
D.assert(() => {
|
|
if (rescheduling) {
|
|
D.assert(() => {
|
|
if (debugCurrentCallbackStack == null) {
|
|
throw new UIWidgetsError(
|
|
"scheduleFrameCallback called with rescheduling true, but no callback is in scope.\n" +
|
|
"The \"rescheduling\" argument should only be set to true if the " +
|
|
"callback is being reregistered from within the callback itself, " +
|
|
"and only then if the callback itself is entirely synchronous. " +
|
|
"If this is the initial registration of the callback, or if the " +
|
|
"callback is asynchronous, then do not use the \"rescheduling\" " +
|
|
"argument.");
|
|
}
|
|
|
|
return true;
|
|
});
|
|
debugStack = debugCurrentCallbackStack;
|
|
} else {
|
|
debugStack = "skipped, use StackTraceUtility.ExtractStackTrace() if you need it"; // StackTraceUtility.ExtractStackTrace();
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
public readonly FrameCallback callback;
|
|
|
|
internal static string debugCurrentCallbackStack;
|
|
internal string debugStack;
|
|
}
|
|
|
|
public enum SchedulerPhase {
|
|
idle,
|
|
transientCallbacks,
|
|
midFrameMicrotasks,
|
|
persistentCallbacks,
|
|
postFrameCallbacks,
|
|
}
|
|
|
|
public class SchedulerBinding {
|
|
public static SchedulerBinding instance {
|
|
get {
|
|
D.assert(_instance != null,
|
|
() => "Binding.instance is null. " +
|
|
"This usually happens when there is a callback from outside of UIWidgets. " +
|
|
"Try to use \"using (WindowProvider.of(BuildContext).getScope()) { ... }\" to wrap your code.");
|
|
return _instance;
|
|
}
|
|
|
|
set {
|
|
if (value == null) {
|
|
D.assert(_instance != null, () => "Binding.instance is already cleared.");
|
|
_instance = null;
|
|
} else {
|
|
D.assert(_instance == null, () => "Binding.instance is already assigned.");
|
|
_instance = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static SchedulerBinding _instance;
|
|
|
|
public SchedulerBinding() {
|
|
Window.instance.onBeginFrame += _handleBeginFrame;
|
|
Window.instance.onDrawFrame += _handleDrawFrame;
|
|
}
|
|
|
|
public float timeDilation {
|
|
get { return _timeDilation; }
|
|
set {
|
|
if (_timeDilation == value) {
|
|
return;
|
|
}
|
|
|
|
resetEpoch();
|
|
_timeDilation = value;
|
|
}
|
|
}
|
|
|
|
float _timeDilation = 1.0f;
|
|
|
|
int _nextFrameCallbackId = 0;
|
|
Dictionary<int, _FrameCallbackEntry> _transientCallbacks = new Dictionary<int, _FrameCallbackEntry>();
|
|
readonly HashSet<int> _removedIds = new HashSet<int>();
|
|
|
|
public int transientCallbackCount {
|
|
get { return _transientCallbacks.Count; }
|
|
}
|
|
|
|
public int scheduleFrameCallback(FrameCallback callback, bool rescheduling = false) {
|
|
scheduleFrame();
|
|
_nextFrameCallbackId += 1;
|
|
_transientCallbacks[_nextFrameCallbackId] =
|
|
new _FrameCallbackEntry(callback, rescheduling: rescheduling);
|
|
return _nextFrameCallbackId;
|
|
}
|
|
|
|
public void cancelFrameCallbackWithId(int id) {
|
|
D.assert(id > 0);
|
|
_transientCallbacks.Remove(id);
|
|
_removedIds.Add(id);
|
|
}
|
|
|
|
readonly List<FrameCallback> _persistentCallbacks = new List<FrameCallback>();
|
|
|
|
public void addPersistentFrameCallback(FrameCallback callback) {
|
|
_persistentCallbacks.Add(callback);
|
|
}
|
|
|
|
readonly List<FrameCallback> _postFrameCallbacks = new List<FrameCallback>();
|
|
|
|
public void addPostFrameCallback(FrameCallback callback) {
|
|
_postFrameCallbacks.Add(callback);
|
|
}
|
|
|
|
public bool hasScheduledFrame {
|
|
get { return _hasScheduledFrame; }
|
|
}
|
|
|
|
bool _hasScheduledFrame = false;
|
|
|
|
public SchedulerPhase schedulerPhase {
|
|
get { return _schedulerPhase; }
|
|
}
|
|
|
|
SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
|
|
|
|
public bool framesEnabled {
|
|
get { return _framesEnabled; }
|
|
set {
|
|
if (_framesEnabled == value) {
|
|
return;
|
|
}
|
|
|
|
_framesEnabled = value;
|
|
if (value) {
|
|
scheduleFrame();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _framesEnabled = true; // todo: set it to false when app switched to background
|
|
|
|
public void ensureVisualUpdate() {
|
|
switch (schedulerPhase) {
|
|
case SchedulerPhase.idle:
|
|
case SchedulerPhase.postFrameCallbacks:
|
|
scheduleFrame();
|
|
return;
|
|
case SchedulerPhase.transientCallbacks:
|
|
case SchedulerPhase.midFrameMicrotasks:
|
|
case SchedulerPhase.persistentCallbacks:
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void scheduleFrame() {
|
|
if (_hasScheduledFrame || !_framesEnabled) {
|
|
return;
|
|
}
|
|
|
|
D.assert(() => {
|
|
if (scheduler_.debugPrintScheduleFrameStacks) {
|
|
Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", schedulerPhase);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
Window.instance.scheduleFrame();
|
|
_hasScheduledFrame = true;
|
|
}
|
|
|
|
public void scheduleForcedFrame() {
|
|
if (_hasScheduledFrame) {
|
|
return;
|
|
}
|
|
|
|
D.assert(() => {
|
|
if (scheduler_.debugPrintScheduleFrameStacks) {
|
|
Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", schedulerPhase);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
Window.instance.scheduleFrame();
|
|
_hasScheduledFrame = true;
|
|
}
|
|
|
|
TimeSpan? _firstRawTimeStampInEpoch;
|
|
TimeSpan _epochStart = TimeSpan.Zero;
|
|
TimeSpan _lastRawTimeStamp = TimeSpan.Zero;
|
|
|
|
public void resetEpoch() {
|
|
_epochStart = _adjustForEpoch(_lastRawTimeStamp);
|
|
_firstRawTimeStampInEpoch = null;
|
|
}
|
|
|
|
TimeSpan _adjustForEpoch(TimeSpan rawTimeStamp) {
|
|
var rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null
|
|
? TimeSpan.Zero
|
|
: rawTimeStamp - _firstRawTimeStampInEpoch.Value;
|
|
return new TimeSpan((long) (rawDurationSinceEpoch.Ticks / timeDilation) + _epochStart.Ticks);
|
|
}
|
|
|
|
public TimeSpan currentFrameTimeStamp {
|
|
get { return _currentFrameTimeStamp.Value; }
|
|
}
|
|
|
|
TimeSpan? _currentFrameTimeStamp;
|
|
|
|
int _profileFrameNumber = 0;
|
|
string _debugBanner;
|
|
|
|
void _handleBeginFrame(TimeSpan rawTimeStamp) {
|
|
handleBeginFrame(rawTimeStamp);
|
|
}
|
|
|
|
void _handleDrawFrame() {
|
|
handleDrawFrame();
|
|
}
|
|
|
|
public void handleBeginFrame(TimeSpan? rawTimeStamp) {
|
|
_firstRawTimeStampInEpoch = _firstRawTimeStampInEpoch ?? rawTimeStamp;
|
|
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
|
|
|
|
if (rawTimeStamp != null) {
|
|
_lastRawTimeStamp = rawTimeStamp.Value;
|
|
}
|
|
|
|
D.assert(() => {
|
|
_profileFrameNumber += 1;
|
|
return true;
|
|
});
|
|
|
|
D.assert(() => {
|
|
if (scheduler_.debugPrintBeginFrameBanner || scheduler_.debugPrintEndFrameBanner) {
|
|
var frameTimeStampDescription = new StringBuilder();
|
|
if (rawTimeStamp != null) {
|
|
_debugDescribeTimeStamp(
|
|
_currentFrameTimeStamp.Value, frameTimeStampDescription);
|
|
} else {
|
|
frameTimeStampDescription.Append("(warm-up frame)");
|
|
}
|
|
|
|
_debugBanner =
|
|
$"▄▄▄▄▄▄▄▄ Frame {_profileFrameNumber.ToString().PadRight(7)} {frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄";
|
|
if (scheduler_.debugPrintBeginFrameBanner) {
|
|
Debug.Log(_debugBanner);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
D.assert(_schedulerPhase == SchedulerPhase.idle);
|
|
_hasScheduledFrame = false;
|
|
|
|
try {
|
|
_schedulerPhase = SchedulerPhase.transientCallbacks;
|
|
var callbacks = _transientCallbacks;
|
|
_transientCallbacks = new Dictionary<int, _FrameCallbackEntry>();
|
|
foreach (var entry in callbacks) {
|
|
if (!_removedIds.Contains(entry.Key)) {
|
|
_invokeFrameCallback(
|
|
entry.Value.callback, _currentFrameTimeStamp.Value, entry.Value.debugStack);
|
|
}
|
|
}
|
|
|
|
_removedIds.Clear();
|
|
} finally {
|
|
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
|
|
}
|
|
}
|
|
|
|
|
|
public void handleDrawFrame() {
|
|
D.assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
|
|
|
|
try {
|
|
_schedulerPhase = SchedulerPhase.persistentCallbacks;
|
|
foreach (FrameCallback callback in _persistentCallbacks) {
|
|
_invokeFrameCallback(callback, _currentFrameTimeStamp.Value);
|
|
}
|
|
|
|
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
|
|
var localPostFrameCallbacks = new List<FrameCallback>(_postFrameCallbacks);
|
|
_postFrameCallbacks.Clear();
|
|
foreach (FrameCallback callback in localPostFrameCallbacks) {
|
|
_invokeFrameCallback(callback, _currentFrameTimeStamp.Value);
|
|
}
|
|
} finally {
|
|
_schedulerPhase = SchedulerPhase.idle;
|
|
D.assert(() => {
|
|
if (scheduler_.debugPrintEndFrameBanner) {
|
|
Debug.Log(new string('▀', _debugBanner.Length));
|
|
}
|
|
|
|
_debugBanner = null;
|
|
return true;
|
|
});
|
|
_currentFrameTimeStamp = null;
|
|
}
|
|
}
|
|
|
|
static void _debugDescribeTimeStamp(TimeSpan timeStamp, StringBuilder buffer) {
|
|
if (timeStamp.TotalDays > 0) {
|
|
buffer.AppendFormat("{0}d ", timeStamp.Days);
|
|
}
|
|
|
|
if (timeStamp.TotalHours > 0) {
|
|
buffer.AppendFormat("{0}h ", timeStamp.Hours);
|
|
}
|
|
|
|
if (timeStamp.TotalMinutes > 0) {
|
|
buffer.AppendFormat("{0}m ", timeStamp.Minutes);
|
|
}
|
|
|
|
if (timeStamp.TotalSeconds > 0) {
|
|
buffer.AppendFormat("{0}s ", timeStamp.Seconds);
|
|
}
|
|
|
|
buffer.AppendFormat("{0}", timeStamp.Milliseconds);
|
|
|
|
int microseconds = (int) (timeStamp.Ticks % 10000 / 10);
|
|
if (microseconds > 0) {
|
|
buffer.AppendFormat(".{0}", microseconds.ToString().PadLeft(3, '0'));
|
|
}
|
|
|
|
buffer.Append("ms");
|
|
}
|
|
|
|
void _invokeFrameCallback(FrameCallback callback, TimeSpan timeStamp, string callbackStack = null) {
|
|
D.assert(callback != null);
|
|
D.assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
|
|
D.assert(() => {
|
|
_FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
|
|
return true;
|
|
});
|
|
|
|
try {
|
|
callback(timeStamp);
|
|
} catch (Exception ex) {
|
|
UIWidgetsError.reportError(new UIWidgetsErrorDetails(
|
|
exception: ex,
|
|
library: "scheduler library",
|
|
context: "during a scheduler callback",
|
|
informationCollector: callbackStack == null
|
|
? (InformationCollector) null
|
|
: information => {
|
|
information.AppendLine(
|
|
"\nThis exception was thrown in the context of a scheduler callback. " +
|
|
"When the scheduler callback was _registered_ (as opposed to when the " +
|
|
"exception was thrown), this was the stack:"
|
|
);
|
|
UIWidgetsError.defaultStackFilter(callbackStack.TrimEnd().Split('\n'))
|
|
.Each((line) => information.AppendLine(line));
|
|
}
|
|
));
|
|
}
|
|
|
|
D.assert(() => {
|
|
_FrameCallbackEntry.debugCurrentCallbackStack = null;
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
}*/
|