Improves ASCII rasterization speed and simplifies API

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 01:37:04 +01:00
parent 309bf5c699
commit d7692ea325
11 changed files with 120 additions and 47 deletions

View File

@@ -59,7 +59,7 @@ class ColoredChar {
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
}
class AsciiRasterizer extends Rasterizer {
class AsciiRasterizer extends CliRasterizer<dynamic> {
static const double _targetAspectRatio = 4 / 3;
static const int _terminalBackdropArgb = 0xFF009688;
static const int _minimumTerminalColumns = 80;
@@ -121,14 +121,16 @@ class AsciiRasterizer extends Rasterizer {
// Intercept the base render call to initialize our text grid
@override
dynamic render(WolfEngine engine, FrameBuffer buffer) {
dynamic render(WolfEngine engine) {
_engine = engine;
_screen = List.generate(
buffer.height,
(_) =>
List.filled(buffer.width, ColoredChar(' ', ColorPalette.vga32Bit[0])),
engine.frameBuffer.height,
(_) => List.filled(
engine.frameBuffer.width,
ColoredChar(' ', ColorPalette.vga32Bit[0]),
),
);
return super.render(engine, buffer);
return super.render(engine);
}
@override
@@ -630,7 +632,7 @@ class AsciiRasterizer extends Rasterizer {
}
@override
String finalizeFrame() {
dynamic finalizeFrame() {
if (_engine.player.damageFlash > 0.0) {
if (isTerminal) {
_applyDamageFlashToScene();
@@ -640,8 +642,9 @@ class AsciiRasterizer extends Rasterizer {
}
if (isTerminal) {
_composeTerminalScene();
return toAnsiString();
}
return toAnsiString();
return _screen;
}
// --- PRIVATE HUD DRAWING HELPERS ---
@@ -787,7 +790,7 @@ class AsciiRasterizer extends Rasterizer {
}
/// Converts the current frame to a single printable ANSI string
String toAnsiString() {
StringBuffer toAnsiString() {
StringBuffer buffer = StringBuffer();
int? lastForeground;
@@ -828,6 +831,6 @@ class AsciiRasterizer extends Rasterizer {
// Reset the terminal color at the very end
buffer.write('\x1b[0m');
return buffer.toString();
return buffer;
}
}

View File

@@ -0,0 +1,36 @@
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
/// Shared terminal orchestration for CLI rasterizers.
abstract class CliRasterizer<T> extends Rasterizer<T> {
/// Resolves the framebuffer dimensions required by this renderer.
///
/// The default uses the full terminal size.
({int width, int height}) terminalFrameBufferSize(int columns, int rows) {
return (width: columns, height: rows);
}
/// Applies terminal-size policy and updates the engine framebuffer.
///
/// Returns `false` when the terminal is too small for this renderer.
bool prepareTerminalFrame(
WolfEngine engine, {
required int columns,
required int rows,
}) {
if (!isTerminalSizeSupported(columns, rows)) {
return false;
}
final size = terminalFrameBufferSize(columns, rows);
engine.setFrameBuffer(size.width, size.height);
return true;
}
/// Builds the standard terminal size warning shown by the CLI host.
String buildTerminalSizeWarning({required int columns, required int rows}) {
return '\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n'
'$terminalSizeRequirement\n'
'Current size: \x1b[33m${columns}x$rows\x1b[0m\n\n'
'Please resize your window to resume the game...';
}
}

View File

@@ -4,7 +4,7 @@ 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_entities.dart';
abstract class Rasterizer {
abstract class Rasterizer<T> {
late List<double> zBuffer;
late int width;
late int height;
@@ -40,9 +40,9 @@ abstract class Rasterizer {
/// The main entry point called by the game loop.
/// Orchestrates the mathematical rendering pipeline.
dynamic render(WolfEngine engine, FrameBuffer buffer) {
width = buffer.width;
height = buffer.height;
T render(WolfEngine engine) {
width = engine.frameBuffer.width;
height = engine.frameBuffer.height;
// The 3D view typically takes up the top 80% of the screen
viewHeight = (height * 0.8).toInt();
zBuffer = List.filled(projectionWidth, 0.0);
@@ -101,7 +101,7 @@ abstract class Rasterizer {
void drawHud(WolfEngine engine);
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
dynamic finalizeFrame();
T finalizeFrame();
// ===========================================================================
// SHARED LIGHTING MATH

View File

@@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
class SixelRasterizer extends Rasterizer {
class SixelRasterizer extends CliRasterizer<String> {
static const double _targetAspectRatio = 4 / 3;
static const int _defaultLineHeightPx = 18;
static const double _defaultCellWidthToHeight = 0.55;
@@ -85,12 +85,18 @@ class SixelRasterizer extends Rasterizer {
}
@override
dynamic render(WolfEngine engine, FrameBuffer buffer) {
String render(WolfEngine engine) {
_engine = engine;
final FrameBuffer scaledBuffer = _createScaledBuffer(buffer);
final FrameBuffer originalBuffer = engine.frameBuffer;
final FrameBuffer scaledBuffer = _createScaledBuffer(originalBuffer);
// We only need 8-bit indices for the 256 VGA colors
_screen = Uint8List(scaledBuffer.width * scaledBuffer.height);
return super.render(engine, scaledBuffer);
engine.frameBuffer = scaledBuffer;
try {
return super.render(engine);
} finally {
engine.frameBuffer = originalBuffer;
}
}
@override

View File

@@ -3,16 +3,16 @@ import 'dart:math' as math;
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
class SoftwareRasterizer extends Rasterizer {
class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
late FrameBuffer _buffer;
late WolfEngine _engine;
// Intercept the base render call to store our references
@override
dynamic render(WolfEngine engine, FrameBuffer buffer) {
FrameBuffer render(WolfEngine engine) {
_engine = engine;
_buffer = buffer;
return super.render(engine, buffer);
_buffer = engine.frameBuffer;
return super.render(engine);
}
@override

View File

@@ -18,6 +18,7 @@ class WolfEngine {
required this.onGameWon,
required this.audio,
required this.input,
required this.frameBuffer,
}) : doorManager = DoorManager(
onPlaySound: (sfxId) => audio.playSoundEffect(sfxId),
);
@@ -48,6 +49,9 @@ class WolfEngine {
/// Polls and processes raw user input into actionable engine commands.
final Wolf3dInput input;
/// The shared render target used by all renderer frontends.
FrameBuffer frameBuffer;
/// Handles the detection and movement of secret "Pushwalls".
final PushwallManager pushwallManager = PushwallManager();
@@ -86,6 +90,17 @@ class WolfEngine {
isInitialized = true;
}
/// Replaces the shared framebuffer when dimensions change.
void setFrameBuffer(int width, int height) {
if (width <= 0 || height <= 0) {
throw ArgumentError('FrameBuffer dimensions must be greater than zero.');
}
if (frameBuffer.width == width && frameBuffer.height == height) {
return;
}
frameBuffer = FrameBuffer(width, height);
}
/// The primary heartbeat of the engine.
///
/// Updates all world subsystems based on the [elapsed] time.

View File

@@ -11,6 +11,7 @@ export 'src/engine/managers/pushwall_manager.dart';
export 'src/engine/player/player.dart';
export 'src/engine/rasterizer/ascii_rasterizer.dart'
show AsciiRasterizer, ColoredChar;
export 'src/engine/rasterizer/cli_rasterizer.dart';
export 'src/engine/rasterizer/rasterizer.dart';
export 'src/engine/rasterizer/sixel_rasterizer.dart';
export 'src/engine/rasterizer/software_rasterizer.dart';