Refactor and enhance documentation across the Wolf3D project

- Updated library imports to use the correct package paths for consistency.
- Added detailed documentation comments to various classes and methods, improving code readability and maintainability.
- Refined the GameSelectScreen, SpriteGallery, and VgaGallery classes with clearer descriptions of their functionality.
- Enhanced the CliInput class to better explain the input handling process and its interaction with the engine.
- Improved the SixelRasterizer and Opl2Emulator classes with comprehensive comments on their operations and state management.
- Removed the deprecated wolf_3d.dart file and consolidated its functionality into wolf_3d_flutter.dart for a cleaner architecture.
- Updated the Wolf3dFlutterInput class to clarify its role in merging keyboard and pointer events.
- Enhanced the rendering classes to provide better context on their purpose and usage within the Flutter framework.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 10:01:12 +01:00
parent 28938f7301
commit 3c6a4672f7
23 changed files with 404 additions and 183 deletions

View File

@@ -1,21 +1,30 @@
/// Shared Flutter renderer shell for driving the Wolf3D engine from a widget tree.
library;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
// 1. The widget now only requires the engine!
/// Base widget for renderers that present frames from a [WolfEngine].
abstract class BaseWolfRenderer extends StatefulWidget {
/// Engine instance that owns world state and the shared framebuffer.
final WolfEngine engine;
/// Creates a renderer bound to [engine].
const BaseWolfRenderer({
required this.engine,
super.key,
});
}
/// Base [State] implementation that provides a ticker-driven render loop.
abstract class BaseWolfRendererState<T extends BaseWolfRenderer>
extends State<T>
with SingleTickerProviderStateMixin {
/// Per-frame ticker used to advance the engine and request renders.
late final Ticker gameLoop;
/// Focus node used by the enclosing [KeyboardListener].
final FocusNode focusNode = FocusNode();
Duration _lastTick = Duration.zero;
@@ -50,8 +59,13 @@ abstract class BaseWolfRendererState<T extends BaseWolfRenderer>
super.dispose();
}
/// Renders the latest engine state into the concrete renderer's output type.
void performRender();
/// Builds the visible viewport widget for the latest rendered frame.
Widget buildViewport(BuildContext context);
/// Background color used by the surrounding scaffold.
Color get scaffoldColor;
@override

View File

@@ -1,8 +1,13 @@
/// Flutter widget that renders Wolf3D frames using the ASCII rasterizer.
library;
import 'package:flutter/material.dart';
import 'package:wolf_3d_dart/wolf_3d_rasterizer.dart';
import 'package:wolf_3d_renderer/base_renderer.dart';
/// Displays the game using a text-mode approximation of the original renderer.
class WolfAsciiRenderer extends BaseWolfRenderer {
/// Creates an ASCII renderer bound to [engine].
const WolfAsciiRenderer({
required super.engine,
super.key,
@@ -22,6 +27,8 @@ class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
@override
void initState() {
super.initState();
// ASCII output uses a reduced logical framebuffer because glyph rendering
// expands the final view significantly once laid out in Flutter text.
if (widget.engine.frameBuffer.width != _renderWidth ||
widget.engine.frameBuffer.height != _renderHeight) {
widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
@@ -46,9 +53,12 @@ class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
}
}
/// Paints a pre-rasterized ASCII frame using grouped text spans per color run.
class AsciiFrameWidget extends StatelessWidget {
/// Two-dimensional text grid generated by [AsciiRasterizer.render].
final List<List<ColoredChar>> frameData;
/// Creates a widget that displays [frameData].
const AsciiFrameWidget({super.key, required this.frameData});
@override
@@ -65,6 +75,8 @@ class AsciiFrameWidget extends StatelessWidget {
children: frameData.map((row) {
List<TextSpan> optimizedSpans = [];
if (row.isNotEmpty) {
// Merge adjacent cells with the same color to keep the rich
// text tree smaller and reduce per-frame layout overhead.
Color currentColor = Color(row[0].argb);
StringBuffer currentSegment = StringBuffer(row[0].char);

View File

@@ -7,18 +7,26 @@ import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
/// A unified widget to display and cache Wolf3D assets.
class WolfAssetPainter extends StatefulWidget {
/// Decoded sprite source, when painting a sprite asset.
final Sprite? sprite;
/// Decoded VGA image source, when painting a VGA asset.
final VgaImage? vgaImage;
/// Pre-rendered game frame, when painting live gameplay output.
final ui.Image? frame;
/// Creates a painter for a palette-indexed [Sprite].
const WolfAssetPainter.sprite(this.sprite, {super.key})
: vgaImage = null,
frame = null;
/// Creates a painter for a planar VGA image.
const WolfAssetPainter.vga(this.vgaImage, {super.key})
: sprite = null,
frame = null;
/// Creates a painter for an already decoded [ui.Image] frame.
const WolfAssetPainter.frame(this.frame, {super.key})
: sprite = null,
vgaImage = null;
@@ -30,7 +38,8 @@ class WolfAssetPainter extends StatefulWidget {
class _WolfAssetPainterState extends State<WolfAssetPainter> {
ui.Image? _cachedImage;
// Tracks if we should dispose the image to free native memory
// Only images created inside this widget should be disposed here. Frames
// handed in from elsewhere remain owned by their producer.
bool _ownsImage = false;
@override
@@ -58,13 +67,14 @@ class _WolfAssetPainterState extends State<WolfAssetPainter> {
}
Future<void> _prepareImage() async {
// Clean up previous internally generated image
// Dispose previously generated images before creating a replacement so the
// widget does not retain stale native image allocations.
if (_ownsImage && _cachedImage != null) {
_cachedImage!.dispose();
_cachedImage = null;
}
// If a pre-rendered frame is passed in, just use it directly
// Pre-decoded frames can be used as-is and stay owned by the caller.
if (widget.frame != null) {
_ownsImage = false;
if (mounted) {
@@ -86,12 +96,13 @@ class _WolfAssetPainterState extends State<WolfAssetPainter> {
_cachedImage = newImage;
});
} else {
// If the widget was unmounted while building, dispose the unused image
// If the widget was unmounted while work completed, dispose the image
// immediately to avoid leaking native resources.
newImage?.dispose();
}
}
/// Converts a Sprite's 8-bit palette data to a 32-bit RGBA ui.Image
/// Converts a sprite's indexed palette data into a Flutter [ui.Image].
Future<ui.Image> _buildSpriteImage(Sprite sprite) {
final completer = Completer<ui.Image>();
final pixels = Uint8List(64 * 64 * 4); // 4 bytes per pixel (RGBA)
@@ -124,7 +135,7 @@ class _WolfAssetPainterState extends State<WolfAssetPainter> {
return completer.future;
}
/// Converts a VgaImage's planar 8-bit palette data to a 32-bit RGBA ui.Image
/// Converts a planar VGA image into a row-major Flutter [ui.Image].
Future<ui.Image> _buildVgaImage(VgaImage image) {
final completer = Completer<ui.Image>();
final pixels = Uint8List(image.width * image.height * 4);
@@ -185,7 +196,9 @@ class _WolfAssetPainterState extends State<WolfAssetPainter> {
}
class _ImagePainter extends CustomPainter {
/// Image already decoded into Flutter's native image representation.
final ui.Image image;
_ImagePainter(this.image);
@override

View File

@@ -1,3 +1,6 @@
/// Flutter widget that renders Wolf3D frames as native pixel images.
library;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
@@ -6,7 +9,9 @@ import 'package:wolf_3d_dart/wolf_3d_rasterizer.dart';
import 'package:wolf_3d_renderer/base_renderer.dart';
import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart';
/// Presents the software rasterizer output by decoding the shared framebuffer.
class WolfFlutterRenderer extends BaseWolfRenderer {
/// Creates a pixel renderer bound to [engine].
const WolfFlutterRenderer({
required super.engine,
super.key,
@@ -28,6 +33,7 @@ class _WolfFlutterRendererState
@override
void initState() {
super.initState();
// Match the original Wolf3D software resolution for the pixel renderer.
if (widget.engine.frameBuffer.width != _renderWidth ||
widget.engine.frameBuffer.height != _renderHeight) {
widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
@@ -45,6 +51,8 @@ class _WolfFlutterRendererState
final FrameBuffer frameBuffer = widget.engine.frameBuffer;
_rasterizer.render(widget.engine);
// Convert the engine-owned framebuffer into a GPU-friendly ui.Image on
// the Flutter side while preserving nearest-neighbor pixel fidelity.
ui.decodeImageFromPixels(
frameBuffer.pixels.buffer.asUint8List(),
frameBuffer.width,
@@ -64,7 +72,7 @@ class _WolfFlutterRendererState
@override
Widget buildViewport(BuildContext context) {
// If we don't have a frame yet, show the loading state
// Delay painting until at least one decoded frame is available.
if (_renderedFrame == null) {
return const CircularProgressIndicator(color: Colors.white24);
}