feat: Enhance DefaultRendererSettingsPersistence to support scoped settings for CLI and Flutter

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 19:54:22 +01:00
parent 3114700683
commit b980174905
5 changed files with 135 additions and 6 deletions
+3 -1
View File
@@ -111,7 +111,9 @@ void main(List<String> arguments) async {
await engine.audio.init();
engine.init();
final persistence = DefaultRendererSettingsPersistence();
final persistence = DefaultRendererSettingsPersistence(
hostKey: rendererSettingsHostCli,
);
final WolfRendererSettings? saved = await persistence.load();
gameLoop = CliGameLoop(
@@ -1,20 +1,29 @@
/// Native (dart:io) renderer-settings persistence.
library;
import 'dart:convert';
import 'dart:io';
import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings.dart';
import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings_persistence.dart';
import 'package:wolf_3d_dart/src/platform/platform_config_dir.dart';
const String rendererSettingsHostCli = 'cli';
const String rendererSettingsHostFlutter = 'flutter';
/// Persists [WolfRendererSettings] as JSON to the platform config directory.
///
/// Pass an explicit [filePath] to override the default location (useful in tests).
class DefaultRendererSettingsPersistence extends RendererSettingsPersistence
with JsonRendererSettingsPersistence {
DefaultRendererSettingsPersistence({String? filePath}) : _filePath = filePath;
DefaultRendererSettingsPersistence({
String? filePath,
String hostKey = rendererSettingsHostFlutter,
}) : _filePath = filePath,
_hostKey = hostKey;
final String? _filePath;
final String _hostKey;
String? _resolvedPath;
Future<String> _getFilePath() async {
@@ -29,7 +38,23 @@ class DefaultRendererSettingsPersistence extends RendererSettingsPersistence
final String path = await _getFilePath();
final File f = File(path);
if (!f.existsSync()) return null;
return await f.readAsString();
final String raw = await f.readAsString();
final Object? decoded = jsonDecode(raw);
if (decoded is! Map<String, Object?>) {
return null;
}
final Object? rendererSettings = decoded['rendererSettings'];
if (rendererSettings is! Map<String, Object?>) {
return null;
}
final Object? scoped = rendererSettings[_hostKey];
if (scoped is! Map<String, Object?>) {
return null;
}
return jsonEncode(scoped);
} catch (_) {
return null;
}
@@ -41,7 +66,36 @@ class DefaultRendererSettingsPersistence extends RendererSettingsPersistence
final String path = await _getFilePath();
final Directory dir = File(path).parent;
if (!dir.existsSync()) await dir.create(recursive: true);
await File(path).writeAsString(json, flush: true);
final File f = File(path);
Map<String, Object?> root = <String, Object?>{};
if (f.existsSync()) {
try {
final Object? existing = jsonDecode(await f.readAsString());
if (existing is Map<String, Object?>) {
root = Map<String, Object?>.from(existing);
}
} catch (_) {
root = <String, Object?>{};
}
}
final Object? scopedDecoded = jsonDecode(json);
if (scopedDecoded is! Map<String, Object?>) {
return;
}
final Map<String, Object?> rendererSettings =
root['rendererSettings'] is Map<String, Object?>
? Map<String, Object?>.from(
root['rendererSettings']! as Map<String, Object?>,
)
: <String, Object?>{};
rendererSettings[_hostKey] = Map<String, Object?>.from(scopedDecoded);
root['rendererSettings'] = rendererSettings;
await f.writeAsString(jsonEncode(root), flush: true);
} catch (_) {
// Best-effort.
}
@@ -4,10 +4,16 @@ library;
import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings.dart';
import 'package:wolf_3d_dart/src/engine/rendering/renderer_settings_persistence.dart';
const String rendererSettingsHostCli = 'cli';
const String rendererSettingsHostFlutter = 'flutter';
/// No-op implementation used on web, where dart:io is unavailable.
class DefaultRendererSettingsPersistence extends RendererSettingsPersistence {
// ignore: avoid_unused_constructor_parameters
DefaultRendererSettingsPersistence({String? filePath});
DefaultRendererSettingsPersistence({
String? filePath,
String hostKey = 'flutter',
});
@override
Future<WolfRendererSettings?> load() async => null;
@@ -9,7 +9,10 @@ class GamePersistenceManager {
RendererSettingsPersistence? rendererSettingsPersistence,
SaveGamePersistence? saveGamePersistence,
}) : rendererSettingsPersistence =
rendererSettingsPersistence ?? DefaultRendererSettingsPersistence(),
rendererSettingsPersistence ??
DefaultRendererSettingsPersistence(
hostKey: rendererSettingsHostFlutter,
),
saveGamePersistence =
saveGamePersistence ?? DefaultSaveGamePersistence();
@@ -154,6 +154,70 @@ void main() {
});
});
group('DefaultRendererSettingsPersistence', () {
test('stores separate scoped settings for flutter and cli', () async {
final tempDir = await Directory.systemTemp.createTemp(
'wolf3d-renderer-config-',
);
addTearDown(() async {
if (await tempDir.exists()) {
await tempDir.delete(recursive: true);
}
});
final String path = '${tempDir.path}/settings.json';
final flutterPersistence = DefaultRendererSettingsPersistence(
filePath: path,
hostKey: rendererSettingsHostFlutter,
);
final cliPersistence = DefaultRendererSettingsPersistence(
filePath: path,
hostKey: rendererSettingsHostCli,
);
const flutterSettings = WolfRendererSettings(
mode: WolfRendererMode.hardware,
);
const cliSettings = WolfRendererSettings(mode: WolfRendererMode.sixel);
await flutterPersistence.save(flutterSettings);
await cliPersistence.save(cliSettings);
final loadedFlutter = await flutterPersistence.load();
final loadedCli = await cliPersistence.load();
expect(loadedFlutter, isNotNull);
expect(loadedCli, isNotNull);
expect(loadedFlutter!.mode, flutterSettings.mode);
expect(loadedCli!.mode, cliSettings.mode);
final String raw = await File(path).readAsString();
expect(raw, contains('"rendererSettings"'));
expect(raw, contains('"flutter"'));
expect(raw, contains('"cli"'));
});
test('does not fall back to legacy unscoped renderer payload', () async {
final tempDir = await Directory.systemTemp.createTemp(
'wolf3d-renderer-config-',
);
addTearDown(() async {
if (await tempDir.exists()) {
await tempDir.delete(recursive: true);
}
});
final String path = '${tempDir.path}/settings.json';
await File(path).writeAsString('{"mode":"hardware"}');
final persistence = DefaultRendererSettingsPersistence(
filePath: path,
hostKey: rendererSettingsHostFlutter,
);
expect(await persistence.load(), isNull);
});
});
testWidgets('Wolf3dApp forwards configured directory to no-data screen', (
tester,
) async {