This document outlines patterns for extending and integrating with specbolt components. Following these patterns ensures consistent architecture and maintainable code.
- Observer Pattern
- Component Extension
- Frontend Integration
- New Peripherals
- Visualization Tools
- File Format Support
The primary integration pattern in specbolt is the observer pattern, allowing components to subscribe to events from other components.
The Memory::Listener interface enables components to observe memory access:
// In Memory.hpp
class Memory {
public:
class Listener {
public:
virtual ~Listener() = default;
virtual void on_memory_read(std::uint16_t address) = 0;
virtual void on_memory_write(std::uint16_t address) = 0;
};
void set_listener(Listener *listener);
bool has_listener() const;
private:
Listener *listener_ = nullptr;
};
// Implementation example
class MyMemoryMonitor final : public Memory::Listener {
public:
void on_memory_read(std::uint16_t address) override {
// React to memory read
}
void on_memory_write(std::uint16_t address) override {
// React to memory write
}
};
// Usage
Memory memory;
MyMemoryMonitor monitor;
memory.set_listener(&monitor);- Make listener classes
finalunless they're designed to be subclassed - Consider narrow, focused listeners over broad ones
- Use raw pointers for non-owning references to listeners
- Always check if a listener is set before notifying
When extending existing components:
The project has multiple implementations (v1, v2, v3) of the Z80 CPU, demonstrating different architectural approaches:
// Required interface for Z80 implementations
class Z80Base {
public:
virtual ~Z80Base() = default;
virtual void reset() = 0;
virtual int step() = 0;
virtual RegisterFile ®isters() = 0;
// ...
};
// New implementation (e.g., v4)
class Z80v4 : public Z80Base {
public:
// Implement required interface
};- Consistently implement the Z80Base interface
- Consider performance vs. accuracy tradeoffs
- Document architectural decisions and strategies
- Provide comprehensive test coverage
specbolt supports multiple frontends (SDL, Console, Web). To add a new frontend:
newfrontend/
├── CMakeLists.txt # Build configuration
├── main.cpp # Entry point
└── [frontend-specific] # Custom components
- Include
Spectrumfor emulator functionality - Include
Memory,Z80, and peripherals as needed - Provide input handling and display mechanisms
- Wire up listeners and event handling
#include "spectrum/Spectrum.hpp"
// Other required components
#include <your_frontend_library>
int main(int argc, char *argv[]) {
// Set up emulator
Spectrum spectrum;
// Set up frontend resources
// Main loop
while (running) {
// Handle input
// Run emulation step
spectrum.step();
// Update display
// Manage timing
}
return 0;
}To add new peripherals or hardware components:
// In peripherals/NewDevice.hpp
class NewDevice {
public:
NewDevice();
~NewDevice();
// Device-specific interface
// Optional integration with existing peripherals
void connect_to_memory(Memory &memory);
private:
// Implementation details
};- Add to
CMakeLists.txtin peripherals directory - Update module exports if using C++ modules
- Wire up in the
Spectrumclass if it's a core peripheral
For visualization components like the memory heatmap:
// Listener component
class VisualizationListener : public SomeComponent::Listener {
// Implement event handlers
};
// Renderer component
class VisualizationRenderer {
public:
// Setup and rendering methods
void render(/* rendering context */);
// Configuration methods
void set_some_option(/* option values */);
private:
VisualizationListener listener_;
// Rendering resources
};- Separate data collection from visualization
- Provide interactive controls for configuration
- Use consistent initialization patterns
- Support enable/disable functionality
- Follow specbolt naming conventions
To add support for new file formats:
// In spectrum/Formats/NewFormat.hpp
class NewFormatHandler {
public:
static bool load(Spectrum &spectrum, const std::string &filename);
static bool save(const Spectrum &spectrum, const std::string &filename);
// Format detection
static bool is_valid_format(const std::string &filename);
};- Add format detection in
Assetsclass - Update CMakeLists.txt to include new files
- Document the format support in README.md
- Use British English spelling throughout the codebase
- Apply const correctness following the style guide
- Prefer single-phase initialization when possible
- Use the proper scope for variables with if-init where appropriate
- Make variables const by default
- Use std::ranges algorithms where applicable
- Support both module and non-module builds
- Provide adequate unit tests for new components
This document will evolve as new integration patterns emerge in the project.