feat: Refactor game persistence and rendering management for CLI and Flutter hosts
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -7,11 +7,11 @@ library;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:wolf_3d_cli/cli_game_loop.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_audio.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_host.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
/// Restores terminal state before exiting the process with [code].
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/// CLI host adapter for persisting renderer settings to a local file.
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// Persists [WolfRendererSettings] as JSON to a local file.
|
||||
///
|
||||
/// The default path is `~/.wolf3d_cli_settings.json`.
|
||||
/// An alternative [filePath] can be supplied at construction time.
|
||||
class CliRendererSettingsPersistence extends RendererSettingsPersistence
|
||||
with JsonRendererSettingsPersistence {
|
||||
CliRendererSettingsPersistence({String? filePath})
|
||||
: _filePath = filePath ?? '${_platformConfigDir()}/settings.json';
|
||||
|
||||
final String _filePath;
|
||||
|
||||
@override
|
||||
Future<String?> readRaw() async {
|
||||
try {
|
||||
final File f = File(_filePath);
|
||||
if (!f.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return await f.readAsString();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeRaw(String json) async {
|
||||
try {
|
||||
await File(_filePath).writeAsString(json, flush: true);
|
||||
} catch (_) {
|
||||
// Best-effort; never crash the loop on a write failure.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _platformConfigDir() {
|
||||
if (Platform.isLinux) {
|
||||
final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? '';
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d';
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return '$home/Library/Application Support/wolf3d';
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
final String appData = Platform.environment['APPDATA'] ?? '.';
|
||||
return '$appData/wolf3d';
|
||||
}
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return '$home/.config/wolf3d';
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// CLI host adapter for slot-based game save persistence.
|
||||
///
|
||||
/// Files are stored under `~/.wolf3d_saves` by default and named
|
||||
/// `SAVEGAM{slot}.{ext}` where `{ext}` follows the active game version.
|
||||
class CliSaveGamePersistence implements SaveGamePersistence {
|
||||
CliSaveGamePersistence({String? directoryPath})
|
||||
: _directoryPath = directoryPath ?? '${_platformConfigDir()}/saves';
|
||||
|
||||
final String _directoryPath;
|
||||
|
||||
@override
|
||||
Future<Uint8List?> load({
|
||||
required int slot,
|
||||
required GameVersion version,
|
||||
}) async {
|
||||
try {
|
||||
final File file = File(_slotPath(slot, version));
|
||||
if (!file.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return await file.readAsBytes();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> exists({
|
||||
required int slot,
|
||||
required GameVersion version,
|
||||
}) async {
|
||||
try {
|
||||
final File file = File(_slotPath(slot, version));
|
||||
return file.existsSync() && file.lengthSync() > 0;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save({
|
||||
required int slot,
|
||||
required GameVersion version,
|
||||
required Uint8List bytes,
|
||||
}) async {
|
||||
final Directory dir = Directory(_directoryPath);
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
|
||||
await File(_slotPath(slot, version)).writeAsBytes(bytes, flush: true);
|
||||
}
|
||||
|
||||
String _slotPath(int slot, GameVersion version) {
|
||||
final String normalizedSlot = slot.clamp(0, 9).toString();
|
||||
return '$_directoryPath/SAVEGAM$normalizedSlot.${version.fileExtension}';
|
||||
}
|
||||
}
|
||||
|
||||
String _platformConfigDir() {
|
||||
if (Platform.isLinux) {
|
||||
final String xdg = Platform.environment['XDG_CONFIG_HOME'] ?? '';
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return xdg.isNotEmpty ? '$xdg/wolf3d' : '$home/.config/wolf3d';
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return '$home/Library/Application Support/wolf3d';
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
final String appData = Platform.environment['APPDATA'] ?? '.';
|
||||
return '$appData/wolf3d';
|
||||
}
|
||||
final String home = Platform.environment['HOME'] ?? '.';
|
||||
return '$home/.config/wolf3d';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library;
|
||||
|
||||
export 'cli_game_loop_stub.dart' if (dart.library.io) 'cli_game_loop_io.dart';
|
||||
-1
@@ -26,7 +26,6 @@ class CliGameLoop {
|
||||
'engine.input',
|
||||
'CliGameLoop requires a CliInput instance.',
|
||||
),
|
||||
|
||||
primaryRenderer = SixelRenderer(),
|
||||
secondaryRenderer = AsciiRenderer(
|
||||
mode: AsciiRendererMode.terminalAnsi,
|
||||
@@ -0,0 +1,37 @@
|
||||
/// Web-safe stub for CLI game loop APIs.
|
||||
library;
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_renderer.dart';
|
||||
|
||||
class CliGameLoop {
|
||||
CliGameLoop({
|
||||
required this.engine,
|
||||
required this.onExit,
|
||||
this.persistence,
|
||||
this.initialSettings,
|
||||
}) : input = engine.input is CliInput
|
||||
? engine.input as CliInput
|
||||
: throw ArgumentError.value(
|
||||
engine.input,
|
||||
'engine.input',
|
||||
'CliGameLoop requires a CliInput instance.',
|
||||
),
|
||||
primaryRenderer = SixelRenderer(),
|
||||
secondaryRenderer = AsciiRenderer(mode: AsciiRendererMode.terminalAnsi);
|
||||
|
||||
final WolfEngine engine;
|
||||
final CliRendererBackend primaryRenderer;
|
||||
final CliRendererBackend secondaryRenderer;
|
||||
final CliInput input;
|
||||
final void Function(int code) onExit;
|
||||
final RendererSettingsPersistence? persistence;
|
||||
final WolfRendererSettings? initialSettings;
|
||||
|
||||
Future<void> start() {
|
||||
throw UnsupportedError('CliGameLoop is only available on dart:io hosts.');
|
||||
}
|
||||
|
||||
void stop() {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
library;
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// Coordinates gameplay persistence concerns for host applications.
|
||||
class GamePersistenceManager {
|
||||
/// Creates persistence manager dependencies with overridable adapters.
|
||||
GamePersistenceManager({
|
||||
RendererSettingsPersistence? rendererSettingsPersistence,
|
||||
SaveGamePersistence? saveGamePersistence,
|
||||
}) : rendererSettingsPersistence =
|
||||
rendererSettingsPersistence ?? DefaultRendererSettingsPersistence(),
|
||||
saveGamePersistence =
|
||||
saveGamePersistence ?? DefaultSaveGamePersistence();
|
||||
|
||||
/// Persists and restores runtime renderer settings.
|
||||
final RendererSettingsPersistence rendererSettingsPersistence;
|
||||
|
||||
/// Persists slot-based save game snapshots.
|
||||
final SaveGamePersistence saveGamePersistence;
|
||||
|
||||
/// Loads previously persisted renderer settings.
|
||||
Future<WolfRendererSettings?> loadRendererSettings() {
|
||||
return rendererSettingsPersistence.load();
|
||||
}
|
||||
|
||||
/// Loads persisted renderer settings and applies them to [engine].
|
||||
Future<WolfRendererSettings?> restoreRendererSettings(
|
||||
WolfEngine engine,
|
||||
) async {
|
||||
final WolfRendererSettings? saved = await loadRendererSettings();
|
||||
if (saved != null) {
|
||||
engine.updateRendererSettings(saved);
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
/// Saves current renderer settings.
|
||||
Future<void> saveRendererSettings(WolfRendererSettings settings) {
|
||||
return rendererSettingsPersistence.save(settings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
library;
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// Renderer presentation mode used by host widgets.
|
||||
enum GameRendererMode {
|
||||
/// Software pixel renderer presented via decoded framebuffer images.
|
||||
software,
|
||||
|
||||
/// Text-mode renderer for debugging and retro terminal aesthetics.
|
||||
ascii,
|
||||
|
||||
/// GLSL renderer with optional CRT-style post processing.
|
||||
hardware,
|
||||
}
|
||||
|
||||
/// Maps engine renderer settings to host renderer presentation mode.
|
||||
GameRendererMode gameRendererModeFromSettings(WolfRendererSettings settings) {
|
||||
return switch (settings.mode) {
|
||||
WolfRendererMode.hardware => GameRendererMode.hardware,
|
||||
WolfRendererMode.software => GameRendererMode.software,
|
||||
WolfRendererMode.ascii || WolfRendererMode.sixel => GameRendererMode.ascii,
|
||||
};
|
||||
}
|
||||
|
||||
/// Falls back to software mode when GLSL rendering is unavailable at runtime.
|
||||
void handleGlslUnavailable({
|
||||
required bool isMounted,
|
||||
required GameRendererMode rendererMode,
|
||||
required WolfEngine? engine,
|
||||
}) {
|
||||
if (!isMounted || rendererMode != GameRendererMode.hardware) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WolfEngine? activeEngine = engine;
|
||||
if (activeEngine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeEngine.updateRendererSettings(
|
||||
activeEngine.rendererSettings.copyWith(mode: WolfRendererMode.software),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'ascii_renderer.dart';
|
||||
|
||||
/// Web-safe stub used when dart:io is unavailable.
|
||||
class SixelRenderer extends AsciiRenderer {
|
||||
SixelRenderer() : super(mode: AsciiRendererMode.terminalAnsi);
|
||||
|
||||
bool isSixelSupported = false;
|
||||
|
||||
@override
|
||||
bool isTerminalSizeSupported(int columns, int rows) => false;
|
||||
|
||||
@override
|
||||
String get terminalSizeRequirement =>
|
||||
'Sixel renderer is unavailable on this platform.';
|
||||
|
||||
static Future<bool> checkTerminalSixelSupport({
|
||||
Stream<List<int>>? inputStream,
|
||||
}) async {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/// Shared host-facing helpers for Wolf3D app shells.
|
||||
library;
|
||||
|
||||
export 'src/host/cli_game_loop.dart' show CliGameLoop;
|
||||
export 'src/host/managers/game_persistence_manager.dart'
|
||||
show GamePersistenceManager;
|
||||
export 'src/host/managers/game_renderer_mode_manager.dart'
|
||||
show GameRendererMode, gameRendererModeFromSettings, handleGlslUnavailable;
|
||||
@@ -6,5 +6,6 @@ export 'src/rendering/ascii_renderer.dart'
|
||||
show AsciiRenderer, AsciiRendererMode, AsciiTheme, AsciiThemes, ColoredChar;
|
||||
export 'src/rendering/cli_renderer_backend.dart';
|
||||
export 'src/rendering/renderer_backend.dart';
|
||||
export 'src/rendering/sixel_renderer.dart';
|
||||
export 'src/rendering/sixel_renderer_stub.dart'
|
||||
if (dart.library.io) 'src/rendering/sixel_renderer.dart';
|
||||
export 'src/rendering/software_renderer.dart';
|
||||
|
||||
@@ -1,42 +1,3 @@
|
||||
library;
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// Coordinates gameplay persistence concerns for Flutter hosts.
|
||||
class GamePersistenceManager {
|
||||
/// Creates persistence manager dependencies with overridable adapters.
|
||||
GamePersistenceManager({
|
||||
RendererSettingsPersistence? rendererSettingsPersistence,
|
||||
SaveGamePersistence? saveGamePersistence,
|
||||
}) : rendererSettingsPersistence =
|
||||
rendererSettingsPersistence ?? DefaultRendererSettingsPersistence(),
|
||||
saveGamePersistence =
|
||||
saveGamePersistence ?? DefaultSaveGamePersistence();
|
||||
|
||||
/// Persists and restores runtime renderer settings.
|
||||
final RendererSettingsPersistence rendererSettingsPersistence;
|
||||
|
||||
/// Persists slot-based save game snapshots.
|
||||
final SaveGamePersistence saveGamePersistence;
|
||||
|
||||
/// Loads previously persisted renderer settings.
|
||||
Future<WolfRendererSettings?> loadRendererSettings() {
|
||||
return rendererSettingsPersistence.load();
|
||||
}
|
||||
|
||||
/// Loads persisted renderer settings and applies them to [engine].
|
||||
Future<WolfRendererSettings?> restoreRendererSettings(
|
||||
WolfEngine engine,
|
||||
) async {
|
||||
final WolfRendererSettings? saved = await loadRendererSettings();
|
||||
if (saved != null) {
|
||||
engine.updateRendererSettings(saved);
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
/// Saves current renderer settings.
|
||||
Future<void> saveRendererSettings(WolfRendererSettings settings) {
|
||||
return rendererSettingsPersistence.save(settings);
|
||||
}
|
||||
}
|
||||
export 'package:wolf_3d_dart/wolf_3d_host.dart' show GamePersistenceManager;
|
||||
|
||||
@@ -1,44 +1,4 @@
|
||||
library;
|
||||
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
|
||||
/// Renderer presentation mode used by Flutter host widgets.
|
||||
enum GameRendererMode {
|
||||
/// Software pixel renderer presented via decoded framebuffer images.
|
||||
software,
|
||||
|
||||
/// Text-mode renderer for debugging and retro terminal aesthetics.
|
||||
ascii,
|
||||
|
||||
/// GLSL renderer with optional CRT-style post processing.
|
||||
hardware,
|
||||
}
|
||||
|
||||
/// Maps engine renderer settings to host renderer presentation mode.
|
||||
GameRendererMode gameRendererModeFromSettings(WolfRendererSettings settings) {
|
||||
return switch (settings.mode) {
|
||||
WolfRendererMode.hardware => GameRendererMode.hardware,
|
||||
WolfRendererMode.software => GameRendererMode.software,
|
||||
WolfRendererMode.ascii || WolfRendererMode.sixel => GameRendererMode.ascii,
|
||||
};
|
||||
}
|
||||
|
||||
/// Falls back to software mode when GLSL rendering is unavailable at runtime.
|
||||
void handleGlslUnavailable({
|
||||
required bool isMounted,
|
||||
required GameRendererMode rendererMode,
|
||||
required WolfEngine? engine,
|
||||
}) {
|
||||
if (!isMounted || rendererMode != GameRendererMode.hardware) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WolfEngine? activeEngine = engine;
|
||||
if (activeEngine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeEngine.updateRendererSettings(
|
||||
activeEngine.rendererSettings.copyWith(mode: WolfRendererMode.software),
|
||||
);
|
||||
}
|
||||
export 'package:wolf_3d_dart/wolf_3d_host.dart'
|
||||
show GameRendererMode, gameRendererModeFromSettings, handleGlslUnavailable;
|
||||
|
||||
@@ -44,10 +44,7 @@ class _NoopAudio implements EngineAudio {
|
||||
}
|
||||
|
||||
class _RecordingEngine extends Wolf3dFlutterEngine {
|
||||
_RecordingEngine({required this.audio}) : super(audioBackend: audio);
|
||||
|
||||
@override
|
||||
final _NoopAudio audio;
|
||||
_RecordingEngine({required _NoopAudio audio}) : super(audioBackend: audio);
|
||||
|
||||
int initCallCount = 0;
|
||||
String? lastDirectory;
|
||||
|
||||
Reference in New Issue
Block a user