feat: multi-package loading with base framework support
- Add RuntimeOptions.basePackages for loading framework packages before game package - Add ScriptEngine.loadPackages() for multi-package module merging - LuaDardoScriptEngine merges modules from all packages, game overrides framework - PackageActivationController loads base packages first, then game package - GamePackageManifest parses optional 'base' field - Update docs: README, quick-start, lua-package-format, architecture - Update all test mocks with loadPackages() implementation
This commit is contained in:
@@ -148,6 +148,7 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
||||
resources: _createResourceManager(),
|
||||
scriptEngine: _bootstrapScriptEngine,
|
||||
audio: _createAudioManager(),
|
||||
runtimeOptions: runtimeOptions,
|
||||
resourceManagerFactory: _createResourceManager,
|
||||
audioManagerFactory: _createAudioManager,
|
||||
scriptEngineFactory: _scriptEngineFactory,
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
class RuntimeOptions {
|
||||
const RuntimeOptions({this.runtimeLuaRoot = defaultRuntimeLuaRoot});
|
||||
const RuntimeOptions({
|
||||
this.runtimeLuaRoot = defaultRuntimeLuaRoot,
|
||||
this.basePackages = const [],
|
||||
});
|
||||
|
||||
static const defaultRuntimeLuaRoot = 'assets/runtime/lua';
|
||||
|
||||
final String runtimeLuaRoot;
|
||||
|
||||
// 框架包 gameId 列表,按顺序先于游戏包加载。
|
||||
// 后加载的同名模块覆盖先加载的。
|
||||
final List<String> basePackages;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../audio/runtime_audio_manager.dart';
|
||||
import '../game/runtime_options.dart';
|
||||
import '../models/game_diff.dart';
|
||||
import '../resources/game_resource_manager.dart';
|
||||
import '../scripting/runtime_script_services.dart';
|
||||
@@ -15,6 +16,7 @@ class PackageActivationController {
|
||||
required this.scriptEngine,
|
||||
this.audio,
|
||||
this.runtimeApiVersion = 1,
|
||||
this.runtimeOptions = const RuntimeOptions(),
|
||||
this.store = const StablePackageStore(),
|
||||
this.assetFallback = const AssetGamePackageRepository(),
|
||||
this.resourceManagerFactory,
|
||||
@@ -28,6 +30,7 @@ class PackageActivationController {
|
||||
final ScriptEngine scriptEngine;
|
||||
final RuntimeAudioManager? audio;
|
||||
final int runtimeApiVersion;
|
||||
final RuntimeOptions runtimeOptions;
|
||||
final StablePackageStore store;
|
||||
final GamePackageRepository assetFallback;
|
||||
final GameResourceManager Function()? resourceManagerFactory;
|
||||
@@ -143,12 +146,34 @@ class PackageActivationController {
|
||||
try {
|
||||
await verifier.verify(candidate);
|
||||
_ensureContinue(shouldContinue);
|
||||
|
||||
// 加载 base packages(框架包),按 runtimeOptions.basePackages 顺序。
|
||||
final basePackages = <GamePackage>[];
|
||||
for (final baseId in runtimeOptions.basePackages) {
|
||||
final baseCandidates = await _candidatePackages(
|
||||
baseId,
|
||||
shouldContinue,
|
||||
);
|
||||
for (final baseCandidate in baseCandidates) {
|
||||
try {
|
||||
await verifier.verify(baseCandidate);
|
||||
basePackages.add(baseCandidate);
|
||||
break;
|
||||
} catch (_) {
|
||||
// Try next candidate.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await preparedResources.mount(candidate);
|
||||
_ensureContinue(shouldContinue);
|
||||
await preparedAudio?.mount(candidate);
|
||||
_ensureContinue(shouldContinue);
|
||||
await preparedScriptEngine.loadPackage(
|
||||
candidate,
|
||||
|
||||
// 合并 base + game 包,传给脚本引擎。
|
||||
final allPackages = [...basePackages, candidate];
|
||||
await preparedScriptEngine.loadPackages(
|
||||
allPackages,
|
||||
services: scriptServices,
|
||||
);
|
||||
_ensureContinue(shouldContinue);
|
||||
|
||||
@@ -15,6 +15,7 @@ class GamePackageManifest {
|
||||
this.display = const GameDisplayConfig(),
|
||||
this.resources = const {},
|
||||
this.modules = const {},
|
||||
this.base,
|
||||
});
|
||||
|
||||
final String gameId;
|
||||
@@ -29,6 +30,9 @@ class GamePackageManifest {
|
||||
final Map<String, GameResource> resources;
|
||||
final Map<String, String> modules;
|
||||
|
||||
/// 依赖的框架包 gameId。加载时会先加载框架包,再加载游戏包。
|
||||
final String? base;
|
||||
|
||||
static GamePackageManifest fromJsonString(String source) {
|
||||
return fromMap(jsonDecode(source) as Map<String, Object?>);
|
||||
}
|
||||
@@ -58,6 +62,8 @@ class GamePackageManifest {
|
||||
}
|
||||
}
|
||||
|
||||
final base = map['base'] as String?;
|
||||
|
||||
final defaultLocale = (map['defaultLocale'] as String?) ?? 'en';
|
||||
final supportedLocales = _stringList(
|
||||
map,
|
||||
@@ -89,6 +95,7 @@ class GamePackageManifest {
|
||||
display: display,
|
||||
resources: resources,
|
||||
modules: modules,
|
||||
base: base,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,34 @@ class LuaDardoScriptEngine implements ScriptEngine {
|
||||
Future<void> loadPackage(
|
||||
GamePackage package, {
|
||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||
}) {
|
||||
return loadPackages([package], services: services);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadPackages(
|
||||
List<GamePackage> packages, {
|
||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||
}) async {
|
||||
if (packages.isEmpty) {
|
||||
throw const FormatException('loadPackages requires at least one package');
|
||||
}
|
||||
|
||||
_services = services;
|
||||
_networkRequestCounter = 0;
|
||||
_hostCallCounter = 0;
|
||||
final script = await package.readText(package.manifest.entry);
|
||||
_moduleScripts = {};
|
||||
for (final entry in package.manifest.modules.entries) {
|
||||
_moduleScripts[entry.key] = await package.readText(entry.value);
|
||||
|
||||
// 按顺序加载所有包的模块,后加载的同名模块覆盖先加载的。
|
||||
for (final package in packages) {
|
||||
for (final entry in package.manifest.modules.entries) {
|
||||
_moduleScripts[entry.key] = await package.readText(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 入口脚本使用最后一个包(游戏包)。
|
||||
final entryPackage = packages.last;
|
||||
final script = await entryPackage.readText(entryPackage.manifest.entry);
|
||||
_loadingModules.clear();
|
||||
|
||||
_lua = LuaState.newState();
|
||||
|
||||
@@ -4,11 +4,19 @@ import '../packages/game_package.dart';
|
||||
import 'runtime_script_services.dart';
|
||||
|
||||
abstract interface class ScriptEngine {
|
||||
// 加载单个包(向后兼容,内部调 loadPackages([package]))。
|
||||
Future<void> loadPackage(
|
||||
GamePackage package, {
|
||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||
});
|
||||
|
||||
// 加载多个包,按顺序合并模块,后加载的同名模块覆盖先加载的。
|
||||
// 入口脚本使用最后一个包。
|
||||
Future<void> loadPackages(
|
||||
List<GamePackage> packages, {
|
||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||
});
|
||||
|
||||
bool smokeTest(Map<String, Object?> context);
|
||||
|
||||
GameDiff init(Map<String, Object?> context);
|
||||
|
||||
Reference in New Issue
Block a user