diff --git a/apps/wolf_3d_cli/bin/main.dart b/apps/wolf_3d_cli/bin/main.dart index 6ad66ef..57ec2dc 100644 --- a/apps/wolf_3d_cli/bin/main.dart +++ b/apps/wolf_3d_cli/bin/main.dart @@ -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]. diff --git a/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart b/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart deleted file mode 100644 index cafed69..0000000 --- a/apps/wolf_3d_cli/lib/cli_renderer_settings_persistence.dart +++ /dev/null @@ -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 readRaw() async { - try { - final File f = File(_filePath); - if (!f.existsSync()) { - return null; - } - return await f.readAsString(); - } catch (_) { - return null; - } - } - - @override - Future 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'; -} diff --git a/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart b/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart deleted file mode 100644 index 0f3f168..0000000 --- a/apps/wolf_3d_cli/lib/cli_save_game_persistence.dart +++ /dev/null @@ -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 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 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 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'; -} diff --git a/packages/wolf_3d_dart/lib/src/host/cli_game_loop.dart b/packages/wolf_3d_dart/lib/src/host/cli_game_loop.dart new file mode 100644 index 0000000..496f5dc --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/host/cli_game_loop.dart @@ -0,0 +1,3 @@ +library; + +export 'cli_game_loop_stub.dart' if (dart.library.io) 'cli_game_loop_io.dart'; diff --git a/apps/wolf_3d_cli/lib/cli_game_loop.dart b/packages/wolf_3d_dart/lib/src/host/cli_game_loop_io.dart similarity index 99% rename from apps/wolf_3d_cli/lib/cli_game_loop.dart rename to packages/wolf_3d_dart/lib/src/host/cli_game_loop_io.dart index a49cea5..0a3e7b0 100644 --- a/apps/wolf_3d_cli/lib/cli_game_loop.dart +++ b/packages/wolf_3d_dart/lib/src/host/cli_game_loop_io.dart @@ -26,7 +26,6 @@ class CliGameLoop { 'engine.input', 'CliGameLoop requires a CliInput instance.', ), - primaryRenderer = SixelRenderer(), secondaryRenderer = AsciiRenderer( mode: AsciiRendererMode.terminalAnsi, diff --git a/packages/wolf_3d_dart/lib/src/host/cli_game_loop_stub.dart b/packages/wolf_3d_dart/lib/src/host/cli_game_loop_stub.dart new file mode 100644 index 0000000..ea3b41e --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/host/cli_game_loop_stub.dart @@ -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 start() { + throw UnsupportedError('CliGameLoop is only available on dart:io hosts.'); + } + + void stop() {} +} diff --git a/packages/wolf_3d_dart/lib/src/host/managers/game_persistence_manager.dart b/packages/wolf_3d_dart/lib/src/host/managers/game_persistence_manager.dart new file mode 100644 index 0000000..c185e62 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/host/managers/game_persistence_manager.dart @@ -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 loadRendererSettings() { + return rendererSettingsPersistence.load(); + } + + /// Loads persisted renderer settings and applies them to [engine]. + Future restoreRendererSettings( + WolfEngine engine, + ) async { + final WolfRendererSettings? saved = await loadRendererSettings(); + if (saved != null) { + engine.updateRendererSettings(saved); + } + return saved; + } + + /// Saves current renderer settings. + Future saveRendererSettings(WolfRendererSettings settings) { + return rendererSettingsPersistence.save(settings); + } +} diff --git a/packages/wolf_3d_dart/lib/src/host/managers/game_renderer_mode_manager.dart b/packages/wolf_3d_dart/lib/src/host/managers/game_renderer_mode_manager.dart new file mode 100644 index 0000000..f638920 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/host/managers/game_renderer_mode_manager.dart @@ -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), + ); +} diff --git a/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer_stub.dart b/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer_stub.dart new file mode 100644 index 0000000..8975afc --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer_stub.dart @@ -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 checkTerminalSixelSupport({ + Stream>? inputStream, + }) async { + return false; + } +} diff --git a/packages/wolf_3d_dart/lib/wolf_3d_host.dart b/packages/wolf_3d_dart/lib/wolf_3d_host.dart new file mode 100644 index 0000000..c4ff9f8 --- /dev/null +++ b/packages/wolf_3d_dart/lib/wolf_3d_host.dart @@ -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; diff --git a/packages/wolf_3d_dart/lib/wolf_3d_renderer.dart b/packages/wolf_3d_dart/lib/wolf_3d_renderer.dart index 1ec8a60..07877f1 100644 --- a/packages/wolf_3d_dart/lib/wolf_3d_renderer.dart +++ b/packages/wolf_3d_dart/lib/wolf_3d_renderer.dart @@ -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'; diff --git a/packages/wolf_3d_flutter/lib/managers/game_persistence_manager.dart b/packages/wolf_3d_flutter/lib/managers/game_persistence_manager.dart index 42a9555..834b052 100644 --- a/packages/wolf_3d_flutter/lib/managers/game_persistence_manager.dart +++ b/packages/wolf_3d_flutter/lib/managers/game_persistence_manager.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 loadRendererSettings() { - return rendererSettingsPersistence.load(); - } - - /// Loads persisted renderer settings and applies them to [engine]. - Future restoreRendererSettings( - WolfEngine engine, - ) async { - final WolfRendererSettings? saved = await loadRendererSettings(); - if (saved != null) { - engine.updateRendererSettings(saved); - } - return saved; - } - - /// Saves current renderer settings. - Future saveRendererSettings(WolfRendererSettings settings) { - return rendererSettingsPersistence.save(settings); - } -} +export 'package:wolf_3d_dart/wolf_3d_host.dart' show GamePersistenceManager; diff --git a/packages/wolf_3d_flutter/lib/managers/game_renderer_mode_manager.dart b/packages/wolf_3d_flutter/lib/managers/game_renderer_mode_manager.dart index d5da436..84e4e4d 100644 --- a/packages/wolf_3d_flutter/lib/managers/game_renderer_mode_manager.dart +++ b/packages/wolf_3d_flutter/lib/managers/game_renderer_mode_manager.dart @@ -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; diff --git a/packages/wolf_3d_flutter/test/app_managers_test.dart b/packages/wolf_3d_flutter/test/app_managers_test.dart index 45c4edc..3baff3b 100644 --- a/packages/wolf_3d_flutter/test/app_managers_test.dart +++ b/packages/wolf_3d_flutter/test/app_managers_test.dart @@ -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;