Initial flame_lua_runtime package

This commit is contained in:
gem
2026-06-07 22:53:58 +08:00
commit 733b2fb798
262 changed files with 28439 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
part of 'game_resource_manager.dart';
extension _GameResourceManagerLoading on GameResourceManager {
Future<ui.Image?> _loadImage(
String? keyOrPath, {
required bool failOnError,
bool retain = false,
}) {
if (keyOrPath == null || keyOrPath.isEmpty) {
return Future.value(null);
}
final requestToken = _asyncGate.token;
final requestGeneration = requestToken.generation;
final path = _tryResolve(keyOrPath);
if (path == null) {
return Future.value(null);
}
final existing = _images[path];
if (existing != null) {
final image = existing.image;
if (existing.generation == requestGeneration &&
existing.state == GameResourceState.ready &&
image != null) {
if (retain) {
existing.refCount++;
}
_touch(existing);
return Future.value(image);
}
final inflight = existing.inflight;
if (existing.generation == requestGeneration && inflight != null) {
return failOnError
? _throwIfNull(inflight, keyOrPath)
: inflight.catchError((_) => null);
}
}
final record = _ImageResourceRecord(generation: requestGeneration)
..state = GameResourceState.loading;
_images[path] = record;
final future = _decodeImage(path, record, requestToken, retain: retain);
record.inflight = future;
return failOnError ? _throwIfNull(future, keyOrPath) : future;
}
Future<ui.Image?> _throwIfNull(
Future<ui.Image?> future,
String keyOrPath,
) async {
final image = await future;
if (image == null) {
throw ResourceLoadException('Required image resource failed: $keyOrPath');
}
return image;
}
Future<ui.Image?> _decodeImage(
String path,
_ImageResourceRecord record,
RuntimeAsyncToken requestToken, {
required bool retain,
}) async {
try {
final activePackage = _package;
if (activePackage == null) {
throw StateError('GameResourceManager has no active package');
}
final frame = await _loadLimiter.run(() async {
final bytes = await activePackage.readBytes(path);
final codec = await ui.instantiateImageCodec(
bytes.buffer.asUint8List(),
);
return codec.getNextFrame();
});
record.inflight = null;
if (!_asyncGate.accepts(requestToken) || _images[path] != record) {
frame.image.dispose();
record.state = GameResourceState.disposed;
return null;
}
record
..image = frame.image
..estimatedBytes = frame.image.width * frame.image.height * 4
..state = GameResourceState.ready
..lastError = null;
if (retain) {
record.refCount++;
}
_cacheBytes += record.estimatedBytes;
_touch(record);
_enforceImageBudget();
return frame.image;
} catch (error) {
record.inflight = null;
if (!_asyncGate.accepts(requestToken) || _images[path] != record) {
record.state = GameResourceState.disposed;
return null;
}
record
..state = GameResourceState.failed
..lastError = error;
_diagnostics?.record(
type: RuntimeDiagnosticType.resourceLoadError,
message: 'Image resource failed to load',
error: error,
context: {'path': path, 'generation': requestToken.generation},
);
return null;
}
}
Future<void> _preloadSpine(
String keyOrPath, {
required bool failOnError,
}) async {
final spine = await _createSpineComponent(keyOrPath);
spine?.dispose();
if (failOnError && spine == null) {
throw ResourceLoadException('Required spine resource failed: $keyOrPath');
}
}
Future<SpineComponent?> _createSpineComponent(String? keyOrPath) async {
if (keyOrPath == null || keyOrPath.isEmpty) {
return null;
}
final requestToken = _asyncGate.token;
final activePackage = _package;
if (activePackage == null) {
return null;
}
final resource = activePackage.manifest.resources[keyOrPath];
if (resource == null || resource.type != GameResourceType.spine) {
return null;
}
try {
await initSpineFlutter();
final atlasPath = activePackage.resolveResourcePath(resource.atlas!);
final skeletonPath = activePackage.resolveResourcePath(
resource.skeleton!,
);
final drawable = await _loadLimiter.run(() {
return SkeletonDrawableFlutter.fromMemory(atlasPath, skeletonPath, (
name,
) async {
final bytes = await activePackage.readBytes(name);
return bytes.buffer.asUint8List(
bytes.offsetInBytes,
bytes.lengthInBytes,
);
});
});
if (!_asyncGate.accepts(requestToken)) {
drawable.dispose();
return null;
}
return SpineComponent(drawable);
} catch (error) {
if (!_asyncGate.accepts(requestToken)) {
return null;
}
_diagnostics?.record(
type: RuntimeDiagnosticType.resourceLoadError,
message: 'Spine resource failed to load',
error: error,
context: {'key': keyOrPath, 'generation': requestToken.generation},
);
return null;
}
}
String? _tryResolve(String keyOrPath) {
try {
return resolve(keyOrPath);
} catch (_) {
return null;
}
}
}