Initial flame_lua_runtime package
This commit is contained in:
50
lib/runtime/lifecycle/runtime_async_gate.dart
Normal file
50
lib/runtime/lifecycle/runtime_async_gate.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
class RuntimeAsyncGate {
|
||||
RuntimeAsyncGate({bool initiallyClosed = false}) : _closed = initiallyClosed;
|
||||
|
||||
int _generation = 0;
|
||||
bool _closed;
|
||||
|
||||
int get generation => _generation;
|
||||
|
||||
bool get isOpen => !_closed;
|
||||
|
||||
bool get isClosed => _closed;
|
||||
|
||||
RuntimeAsyncToken get token => RuntimeAsyncToken._(this, _generation);
|
||||
|
||||
RuntimeAsyncToken activate() {
|
||||
_closed = false;
|
||||
_generation++;
|
||||
return token;
|
||||
}
|
||||
|
||||
RuntimeAsyncToken advance() {
|
||||
_generation++;
|
||||
return token;
|
||||
}
|
||||
|
||||
RuntimeAsyncToken close() {
|
||||
_closed = true;
|
||||
_generation++;
|
||||
return token;
|
||||
}
|
||||
|
||||
bool accepts(RuntimeAsyncToken token) {
|
||||
return !_closed &&
|
||||
identical(token._gate, this) &&
|
||||
token.generation == _generation;
|
||||
}
|
||||
|
||||
bool acceptsGeneration(int generation) {
|
||||
return !_closed && generation == _generation;
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeAsyncToken {
|
||||
const RuntimeAsyncToken._(this._gate, this.generation);
|
||||
|
||||
final RuntimeAsyncGate _gate;
|
||||
final int generation;
|
||||
|
||||
bool get isAccepted => _gate.accepts(this);
|
||||
}
|
||||
77
lib/runtime/lifecycle/runtime_serial_queue.dart
Normal file
77
lib/runtime/lifecycle/runtime_serial_queue.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'dart:async' as async;
|
||||
|
||||
class RuntimeSerialQueue<T> {
|
||||
RuntimeSerialQueue({required this.onItem, bool Function()? shouldContinue})
|
||||
: _shouldContinue = shouldContinue;
|
||||
|
||||
final void Function(T item) onItem;
|
||||
final bool Function()? _shouldContinue;
|
||||
final List<T> _queue = [];
|
||||
int _head = 0;
|
||||
bool _disposed = false;
|
||||
bool _draining = false;
|
||||
|
||||
int get pendingCount => _queue.length - _head;
|
||||
|
||||
bool get isDraining => _draining;
|
||||
|
||||
bool get isDisposed => _disposed;
|
||||
|
||||
void enqueue(T item) {
|
||||
if (_disposed || !_canContinue()) {
|
||||
return;
|
||||
}
|
||||
_queue.add(item);
|
||||
_scheduleDrain();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_queue.clear();
|
||||
_head = 0;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
clear();
|
||||
}
|
||||
|
||||
void _scheduleDrain() {
|
||||
if (_draining) {
|
||||
return;
|
||||
}
|
||||
_draining = true;
|
||||
async.scheduleMicrotask(_drain);
|
||||
}
|
||||
|
||||
void _drain() {
|
||||
try {
|
||||
while (pendingCount > 0 && !_disposed && _canContinue()) {
|
||||
onItem(_queue[_head++]);
|
||||
_compactIfNeeded();
|
||||
}
|
||||
} finally {
|
||||
_draining = false;
|
||||
}
|
||||
|
||||
if (pendingCount > 0 && !_disposed && _canContinue()) {
|
||||
_scheduleDrain();
|
||||
}
|
||||
}
|
||||
|
||||
void _compactIfNeeded() {
|
||||
if (_head == 0) {
|
||||
return;
|
||||
}
|
||||
if (_head == _queue.length) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
if (_head < 32 || _head * 2 < _queue.length) {
|
||||
return;
|
||||
}
|
||||
_queue.removeRange(0, _head);
|
||||
_head = 0;
|
||||
}
|
||||
|
||||
bool _canContinue() => _shouldContinue?.call() ?? true;
|
||||
}
|
||||
71
lib/runtime/lifecycle/runtime_session.dart
Normal file
71
lib/runtime/lifecycle/runtime_session.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
enum RuntimeSessionState { created, loading, active, disposing, disposed }
|
||||
|
||||
class RuntimeSession {
|
||||
RuntimeSession({required this.gameId}) : id = _nextId++;
|
||||
|
||||
static int _nextId = 1;
|
||||
|
||||
final int id;
|
||||
final String gameId;
|
||||
RuntimeSessionState _state = RuntimeSessionState.created;
|
||||
|
||||
RuntimeSessionState get state => _state;
|
||||
|
||||
bool get isLoading => _state == RuntimeSessionState.loading;
|
||||
|
||||
bool get isActive => _state == RuntimeSessionState.active;
|
||||
|
||||
bool get isDisposing => _state == RuntimeSessionState.disposing;
|
||||
|
||||
bool get isDisposed => _state == RuntimeSessionState.disposed;
|
||||
|
||||
bool get acceptsWork =>
|
||||
_state != RuntimeSessionState.disposing &&
|
||||
_state != RuntimeSessionState.disposed;
|
||||
|
||||
void beginLoading() {
|
||||
_transition(
|
||||
RuntimeSessionState.loading,
|
||||
allowedFrom: const {RuntimeSessionState.created},
|
||||
);
|
||||
}
|
||||
|
||||
void activate() {
|
||||
_transition(
|
||||
RuntimeSessionState.active,
|
||||
allowedFrom: const {
|
||||
RuntimeSessionState.created,
|
||||
RuntimeSessionState.loading,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void beginDisposing() {
|
||||
if (_state == RuntimeSessionState.disposed ||
|
||||
_state == RuntimeSessionState.disposing) {
|
||||
return;
|
||||
}
|
||||
_state = RuntimeSessionState.disposing;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_state = RuntimeSessionState.disposed;
|
||||
}
|
||||
|
||||
bool accepts(int sessionId) => isActive && id == sessionId;
|
||||
|
||||
bool acceptsWorkFor(int sessionId) => acceptsWork && id == sessionId;
|
||||
|
||||
void _transition(
|
||||
RuntimeSessionState next, {
|
||||
required Set<RuntimeSessionState> allowedFrom,
|
||||
}) {
|
||||
if (_state == next) {
|
||||
return;
|
||||
}
|
||||
if (!allowedFrom.contains(_state)) {
|
||||
throw StateError('Invalid runtime session transition: $_state -> $next');
|
||||
}
|
||||
_state = next;
|
||||
}
|
||||
}
|
||||
129
lib/runtime/lifecycle/runtime_task_registry.dart
Normal file
129
lib/runtime/lifecycle/runtime_task_registry.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'dart:async' as async;
|
||||
|
||||
class RuntimeTaskRegistry<T> {
|
||||
RuntimeTaskRegistry({required this.cancelledValue});
|
||||
|
||||
final T cancelledValue;
|
||||
final Set<RuntimeTask<T>> _tasks = {};
|
||||
final Map<String, Set<RuntimeTask<T>>> _tasksByScope = {};
|
||||
bool _disposed = false;
|
||||
|
||||
int get activeTaskCount => _tasks.length;
|
||||
|
||||
int scopedTaskCount(String scope) => _tasksByScope[scope]?.length ?? 0;
|
||||
|
||||
RuntimeTask<T> create({String? scope}) {
|
||||
if (_disposed) {
|
||||
throw StateError('RuntimeTaskRegistry has been disposed');
|
||||
}
|
||||
|
||||
late final RuntimeTask<T> task;
|
||||
task = RuntimeTask<T>._(
|
||||
scope: scope,
|
||||
cancelledValue: cancelledValue,
|
||||
onComplete: _unregister,
|
||||
);
|
||||
_tasks.add(task);
|
||||
if (scope != null) {
|
||||
_tasksByScope.putIfAbsent(scope, () => {}).add(task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
void cancelScope(String scope) {
|
||||
final tasks = _tasksByScope[scope]?.toList(growable: false) ?? const [];
|
||||
for (final task in tasks) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
final tasks = _tasks.toList(growable: false);
|
||||
for (final task in tasks) {
|
||||
task.cancel();
|
||||
}
|
||||
_tasks.clear();
|
||||
_tasksByScope.clear();
|
||||
}
|
||||
|
||||
void _unregister(RuntimeTask<T> task) {
|
||||
_tasks.remove(task);
|
||||
final scope = task.scope;
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
final scopedTasks = _tasksByScope[scope];
|
||||
scopedTasks?.remove(task);
|
||||
if (scopedTasks != null && scopedTasks.isEmpty) {
|
||||
_tasksByScope.remove(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeTask<T> {
|
||||
RuntimeTask._({
|
||||
required this.scope,
|
||||
required this.cancelledValue,
|
||||
required void Function(RuntimeTask<T> task) onComplete,
|
||||
}) : _onComplete = onComplete;
|
||||
|
||||
final String? scope;
|
||||
final T cancelledValue;
|
||||
final void Function(RuntimeTask<T> task) _onComplete;
|
||||
final async.Completer<T> _completer = async.Completer<T>();
|
||||
final Set<async.Timer> _timers = {};
|
||||
final List<void Function()> _cancelCallbacks = [];
|
||||
bool _cancelled = false;
|
||||
|
||||
Future<T> get future => _completer.future;
|
||||
|
||||
bool get isCancelled => _cancelled;
|
||||
|
||||
void addTimer(async.Timer timer) {
|
||||
if (_cancelled) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
_timers.add(timer);
|
||||
}
|
||||
|
||||
void removeTimer(async.Timer timer) {
|
||||
_timers.remove(timer);
|
||||
}
|
||||
|
||||
void addCancelCallback(void Function() callback) {
|
||||
if (_cancelled) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
_cancelCallbacks.add(callback);
|
||||
}
|
||||
|
||||
void complete(T result) {
|
||||
if (_completer.isCompleted) {
|
||||
return;
|
||||
}
|
||||
_completer.complete(result);
|
||||
_onComplete(this);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (_cancelled) {
|
||||
return;
|
||||
}
|
||||
_cancelled = true;
|
||||
for (final timer in _timers) {
|
||||
timer.cancel();
|
||||
}
|
||||
_timers.clear();
|
||||
for (final callback in _cancelCallbacks) {
|
||||
callback();
|
||||
}
|
||||
_cancelCallbacks.clear();
|
||||
complete(cancelledValue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user