feat: add Lua runtime storage API
This commit is contained in:
@@ -24,6 +24,7 @@ import '../display/runtime_viewport.dart';
|
||||
import '../resources/game_resource_manager.dart';
|
||||
import '../scripting/runtime_script_services.dart';
|
||||
import '../scripting/script_engine.dart';
|
||||
import '../storage/runtime_storage_manager.dart';
|
||||
import 'runtime_locale.dart';
|
||||
import 'runtime_options.dart';
|
||||
|
||||
@@ -73,6 +74,7 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
||||
late final PositionComponent _viewportRoot;
|
||||
RuntimeNetworkManager? _network;
|
||||
RuntimeHostBridgeManager? _hostBridgeManager;
|
||||
RuntimeStorageManager? _storage;
|
||||
RuntimeViewportConfig? _viewportConfig;
|
||||
late final CommandExecutor _commands;
|
||||
RuntimeSession? _session;
|
||||
@@ -117,6 +119,7 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
||||
'initialized': true,
|
||||
'images': _resources.imagesDebugJson(),
|
||||
'audio': _audio.audioDebugJson(),
|
||||
'storage': _storage?.debugJson() ?? const {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,6 +145,8 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
||||
diagnostics: diagnostics,
|
||||
);
|
||||
_hostBridgeManager = hostBridgeManager;
|
||||
final storage = await RuntimeStorageManager.create(gameId: gameId);
|
||||
_storage = storage;
|
||||
final activation =
|
||||
await PackageActivationController(
|
||||
repository: _packageRepository,
|
||||
@@ -155,6 +160,7 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
||||
scriptServices: RuntimeScriptServices(
|
||||
network: network,
|
||||
hostBridge: hostBridgeManager,
|
||||
storage: storage,
|
||||
),
|
||||
store: StablePackageStore(runtimeOptions: runtimeOptions),
|
||||
assetFallback: AssetGamePackageRepository(
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../models/game_diff.dart';
|
||||
import '../models/runtime_event.dart';
|
||||
import '../network/runtime_network_manager.dart';
|
||||
import '../packages/game_package.dart';
|
||||
import '../storage/runtime_storage_manager.dart';
|
||||
import 'runtime_script_services.dart';
|
||||
import 'script_engine.dart';
|
||||
|
||||
@@ -166,6 +167,18 @@ class LuaDardoScriptEngine implements ScriptEngine {
|
||||
_lua.pushDartFunction(_hostRespond);
|
||||
_lua.setField(-2, 'host_respond');
|
||||
|
||||
_lua.pushDartFunction(_storageGet);
|
||||
_lua.setField(-2, 'storage_get');
|
||||
|
||||
_lua.pushDartFunction(_storageSet);
|
||||
_lua.setField(-2, 'storage_set');
|
||||
|
||||
_lua.pushDartFunction(_storageRemove);
|
||||
_lua.setField(-2, 'storage_remove');
|
||||
|
||||
_lua.pushDartFunction(_storageClear);
|
||||
_lua.setField(-2, 'storage_clear');
|
||||
|
||||
_lua.setGlobal('runtime');
|
||||
}
|
||||
|
||||
@@ -272,6 +285,43 @@ class LuaDardoScriptEngine implements ScriptEngine {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int _storageGet(LuaState lua) {
|
||||
final storage = _requireStorage();
|
||||
final key = lua.toStr(1);
|
||||
if (key == null || key.isEmpty) {
|
||||
throw const FormatException('runtime.storage_get(key, defaultValue) requires key');
|
||||
}
|
||||
final defaultValue = _readValue(2);
|
||||
_pushValue(storage.getValue(key, defaultValue));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int _storageSet(LuaState lua) {
|
||||
final storage = _requireStorage();
|
||||
final key = lua.toStr(1);
|
||||
if (key == null || key.isEmpty) {
|
||||
throw const FormatException('runtime.storage_set(key, value) requires key');
|
||||
}
|
||||
final value = _readValue(2);
|
||||
lua.pushBoolean(storage.setValue(key, value));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int _storageRemove(LuaState lua) {
|
||||
final storage = _requireStorage();
|
||||
final key = lua.toStr(1);
|
||||
if (key == null || key.isEmpty) {
|
||||
throw const FormatException('runtime.storage_remove(key) requires key');
|
||||
}
|
||||
lua.pushBoolean(storage.remove(key));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int _storageClear(LuaState lua) {
|
||||
lua.pushBoolean(_requireStorage().clear());
|
||||
return 1;
|
||||
}
|
||||
|
||||
RuntimeHostBridgeManager _requireHostBridge() {
|
||||
final hostBridge = _services.hostBridge;
|
||||
if (hostBridge == null) {
|
||||
@@ -293,6 +343,14 @@ class LuaDardoScriptEngine implements ScriptEngine {
|
||||
return network;
|
||||
}
|
||||
|
||||
RuntimeStorageManager _requireStorage() {
|
||||
final storage = _services.storage;
|
||||
if (storage == null) {
|
||||
throw StateError('Runtime storage service is not installed');
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
String _nextNetworkRequestId(String prefix) {
|
||||
_networkRequestCounter += 1;
|
||||
return '$prefix:$_networkRequestCounter';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import '../host/runtime_host_bridge.dart';
|
||||
import '../network/runtime_network_manager.dart';
|
||||
import '../storage/runtime_storage_manager.dart';
|
||||
|
||||
class RuntimeScriptServices {
|
||||
const RuntimeScriptServices({this.network, this.hostBridge});
|
||||
const RuntimeScriptServices({this.network, this.hostBridge, this.storage});
|
||||
|
||||
final RuntimeNetworkManager? network;
|
||||
final RuntimeHostBridgeManager? hostBridge;
|
||||
final RuntimeStorageManager? storage;
|
||||
}
|
||||
|
||||
94
lib/runtime/storage/runtime_storage_manager.dart
Normal file
94
lib/runtime/storage/runtime_storage_manager.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class RuntimeStorageManager {
|
||||
RuntimeStorageManager._(this._file, this._values);
|
||||
|
||||
final File _file;
|
||||
final Map<String, Object?> _values;
|
||||
|
||||
static Future<RuntimeStorageManager> create({required String gameId}) async {
|
||||
final root = await getApplicationSupportDirectory();
|
||||
final directory = Directory(p.join(root.path, 'flame_lua_storage'));
|
||||
if (!directory.existsSync()) {
|
||||
directory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final file = File(p.join(directory.path, '$gameId.json'));
|
||||
if (!file.existsSync()) {
|
||||
return RuntimeStorageManager._(file, <String, Object?>{});
|
||||
}
|
||||
|
||||
try {
|
||||
final raw = jsonDecode(file.readAsStringSync());
|
||||
if (raw is Map) {
|
||||
return RuntimeStorageManager._(
|
||||
file,
|
||||
Map<String, Object?>.from(raw),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// Corrupt storage should not prevent a game from loading.
|
||||
}
|
||||
|
||||
return RuntimeStorageManager._(file, <String, Object?>{});
|
||||
}
|
||||
|
||||
Object? getValue(String key, [Object? defaultValue]) {
|
||||
if (!_values.containsKey(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return _values[key];
|
||||
}
|
||||
|
||||
bool setValue(String key, Object? value) {
|
||||
_values[key] = _normalize(value);
|
||||
_flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove(String key) {
|
||||
final removed = _values.remove(key) != null;
|
||||
if (removed) {
|
||||
_flush();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
bool clear() {
|
||||
if (_values.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
_values.clear();
|
||||
_flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<String, Object?> debugJson() => Map<String, Object?>.from(_values);
|
||||
|
||||
Object? _normalize(Object? value) {
|
||||
if (value == null || value is bool || value is num || value is String) {
|
||||
return value;
|
||||
}
|
||||
if (value is List) {
|
||||
return value.map(_normalize).toList(growable: false);
|
||||
}
|
||||
if (value is Map) {
|
||||
return {
|
||||
for (final entry in value.entries) entry.key.toString(): _normalize(entry.value),
|
||||
};
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
void _flush() {
|
||||
final parent = _file.parent;
|
||||
if (!parent.existsSync()) {
|
||||
parent.createSync(recursive: true);
|
||||
}
|
||||
_file.writeAsStringSync(jsonEncode(_values));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user