1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-29 12:50:28 +00:00

Merge pull request #779 from TomHarte/6502State

Provisionally adds `State` and `get/set_state` to the 6502.
This commit is contained in:
Thomas Harte 2020-03-31 21:05:29 -04:00 committed by GitHub
commit c4b114133a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 442 additions and 152 deletions

View File

@ -17,6 +17,6 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get --fix-missing install libsdl2-dev scons
- name: Make
run: cd OSBindings/SDL; scons

View File

@ -30,7 +30,7 @@ class MultiStruct: public Reflection::Struct {
}
}
std::vector<std::string> all_keys() final {
std::vector<std::string> all_keys() const final {
std::set<std::string> keys;
for(auto &options: options_) {
const auto new_keys = options->all_keys();
@ -39,7 +39,7 @@ class MultiStruct: public Reflection::Struct {
return std::vector<std::string>(keys.begin(), keys.end());
}
std::vector<std::string> values_for(const std::string &name) final {
std::vector<std::string> values_for(const std::string &name) const final {
std::set<std::string> values;
for(auto &options: options_) {
const auto new_values = options->values_for(name);
@ -48,7 +48,7 @@ class MultiStruct: public Reflection::Struct {
return std::vector<std::string>(values.begin(), values.end());
}
const std::type_info *type_of(const std::string &name) final {
const std::type_info *type_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return info;
@ -56,7 +56,7 @@ class MultiStruct: public Reflection::Struct {
return nullptr;
}
const void *get(const std::string &name) final {
const void *get(const std::string &name) const final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;

View File

@ -15,6 +15,8 @@
#include "../RegisterSizes.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Reflection/Enum.hpp"
#include "../../Reflection/Struct.hpp"
namespace CPU {
namespace MOS6502 {
@ -29,8 +31,7 @@ enum Register {
Flags,
A,
X,
Y,
S
Y
};
/*
@ -128,6 +129,81 @@ class ProcessorBase: public ProcessorStorage {
public:
ProcessorBase(Personality personality) : ProcessorStorage(personality) {}
struct State: public Reflection::StructImpl<State> {
/*!
Provides the current state of the well-known, published internal registers.
*/
struct Registers: public Reflection::StructImpl<Registers> {
uint16_t program_counter;
uint8_t stack_pointer;
uint8_t flags;
uint8_t a, x, y;
Registers();
} registers;
/*!
Provides the current state of the processor's various input lines that aren't
related to an access cycle.
*/
struct Inputs: public Reflection::StructImpl<Inputs> {
bool ready;
bool irq;
bool nmi;
bool reset;
Inputs();
} inputs;
/*!
Contains internal state used by this particular implementation of a 6502. Most of it
does not necessarily correlate with anything in a real 6502, and some of it very
obviously doesn't.
*/
struct ExecutionState: public Reflection::StructImpl<ExecutionState> {
ReflectableEnum(Phase,
Instruction, Stopped, Waiting, Jammed, Ready
);
/// Current executon phase, e.g. standard instruction flow or responding to an IRQ.
Phase phase;
int micro_program;
int micro_program_offset;
// The following are very internal things. At the minute I
// consider these 'reliable' for inter-launch state
// preservation only on the grounds that this implementation
// of a 6502 is now empirically stable.
//
// If cycles_into_phase is 0, the values below need not be
// retained, they're entirely ephemeral. If providing a state
// for persistance, machines that can should advance until
// cycles_into_phase is 0.
uint8_t operation, operand;
uint16_t address, next_address;
ExecutionState();
} execution_state;
State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(execution_state);
DeclareField(inputs);
}
}
};
/*!
Gets current processor state.
*/
State get_state();
/*!
Sets current processor state.
*/
void set_state(const State &);
/*!
Gets the value of a register.
@ -198,6 +274,40 @@ class ProcessorBase: public ProcessorStorage {
bool is_jammed();
};
// Boilerplate follows here, to establish 'reflection' for the state struct defined above.
inline ProcessorBase::State::Registers::Registers() {
if(needs_declare()) {
DeclareField(program_counter);
DeclareField(stack_pointer);
DeclareField(flags);
DeclareField(a);
DeclareField(x);
DeclareField(y);
}
}
inline ProcessorBase::State::ExecutionState::ExecutionState() {
if(needs_declare()) {
AnnounceEnum(Phase);
DeclareField(phase);
DeclareField(micro_program);
DeclareField(micro_program_offset);
DeclareField(operation);
DeclareField(operand);
DeclareField(address);
DeclareField(next_address);
}
}
inline ProcessorBase::State::Inputs::Inputs() {
if(needs_declare()) {
DeclareField(ready);
DeclareField(irq);
DeclareField(nmi);
DeclareField(reset);
}
}
/*!
@abstact Template providing emulation of a 6502 processor.

View File

@ -8,6 +8,8 @@
#include "../6502.hpp"
#include <cassert>
using namespace CPU::MOS6502;
const uint8_t CPU::MOS6502::JamOpcode = 0xf2;
@ -21,7 +23,6 @@ uint16_t ProcessorBase::get_value_of_register(Register r) {
case Register::A: return a_;
case Register::X: return x_;
case Register::Y: return y_;
case Register::S: return s_;
default: return 0;
}
}
@ -34,7 +35,6 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
case Register::A: a_ = static_cast<uint8_t>(value); break;
case Register::X: x_ = static_cast<uint8_t>(value); break;
case Register::Y: y_ = static_cast<uint8_t>(value); break;
case Register::S: s_ = static_cast<uint8_t>(value); break;
default: break;
}
}
@ -42,3 +42,79 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
bool ProcessorBase::is_jammed() {
return is_jammed_;
}
ProcessorBase::State ProcessorBase::get_state() {
ProcessorBase::State state;
// Fill in registers.
state.registers.program_counter = pc_.full;
state.registers.stack_pointer = s_;
state.registers.flags = get_flags();
state.registers.a = a_;
state.registers.x = x_;
state.registers.y = y_;
// Fill in other inputs.
state.inputs.ready = ready_line_is_enabled_;
state.inputs.irq = irq_line_;
state.inputs.nmi = nmi_line_is_enabled_;
state.inputs.reset = interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn);
// Fill in execution state.
state.execution_state.operation = operation_;
state.execution_state.operand = operand_;
state.execution_state.address = address_.full;
state.execution_state.next_address = next_address_.full;
if(ready_is_active_) {
state.execution_state.phase = State::ExecutionState::Phase::Ready;
} else if(is_jammed_) {
state.execution_state.phase = State::ExecutionState::Phase::Jammed;
} else if(wait_is_active_) {
state.execution_state.phase = State::ExecutionState::Phase::Waiting;
} else if(stop_is_active_) {
state.execution_state.phase = State::ExecutionState::Phase::Stopped;
} else {
state.execution_state.phase = State::ExecutionState::Phase::Instruction;
}
const auto micro_offset = size_t(scheduled_program_counter_ - &operations_[0][0]);
const auto list_length = sizeof(InstructionList) / sizeof(MicroOp);
state.execution_state.micro_program = int(micro_offset / list_length);
state.execution_state.micro_program_offset = int(micro_offset % list_length);
assert(&operations_[state.execution_state.micro_program][state.execution_state.micro_program_offset] == scheduled_program_counter_);
return state;
}
void ProcessorBase::set_state(const State &state) {
// Grab registers.
pc_.full = state.registers.program_counter;
s_ = state.registers.stack_pointer;
set_flags(state.registers.flags);
a_ = state.registers.a;
x_ = state.registers.x;
y_ = state.registers.y;
// Grab other inputs.
ready_line_is_enabled_ = state.inputs.ready;
set_irq_line(state.inputs.irq);
set_nmi_line(state.inputs.nmi);
set_reset_line(state.inputs.reset);
// Set execution state.
ready_is_active_ = is_jammed_ = wait_is_active_ = stop_is_active_ = false;
switch(state.execution_state.phase) {
case State::ExecutionState::Phase::Ready: ready_is_active_ = true; break;
case State::ExecutionState::Phase::Jammed: is_jammed_ = true; break;
case State::ExecutionState::Phase::Stopped: stop_is_active_ = true; break;
case State::ExecutionState::Phase::Waiting: wait_is_active_ = true; break;
case State::ExecutionState::Phase::Instruction: break;
}
operation_ = state.execution_state.operation;
operand_ = state.execution_state.operand;
address_.full = state.execution_state.address;
next_address_.full = state.execution_state.next_address;
scheduled_program_counter_ = &operations_[state.execution_state.micro_program][state.execution_state.micro_program_offset];
}

View File

@ -13,17 +13,7 @@
*/
template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::run_for(const Cycles cycles) {
static const MicroOp do_branch[] = {
CycleReadFromPC,
CycleAddSignedOperandToPC,
OperationMoveToNextProgram
};
static uint8_t throwaway_target;
static const MicroOp fetch_decode_execute[] = {
CycleFetchOperation,
CycleFetchOperand,
OperationDecodeOperation
};
// These plus program below act to give the compiler permission to update these values
// without touching the class storage (i.e. it explicitly says they need be completely up
@ -33,28 +23,29 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
uint16_t busAddress = bus_address_;
uint8_t *busValue = bus_value_;
#define checkSchedule(op) \
#define checkSchedule() \
if(!scheduled_program_counter_) {\
if(interrupt_requests_) {\
if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\
interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\
scheduled_program_counter_ = get_reset_program();\
scheduled_program_counter_ = operations_[size_t(OperationsSlot::Reset)];\
} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\
interrupt_requests_ &= ~InterruptRequestFlags::NMI;\
scheduled_program_counter_ = get_nmi_program();\
scheduled_program_counter_ = operations_[size_t(OperationsSlot::NMI)];\
} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\
scheduled_program_counter_ = get_irq_program();\
scheduled_program_counter_ = operations_[size_t(OperationsSlot::IRQ)];\
} \
} else {\
scheduled_program_counter_ = fetch_decode_execute;\
scheduled_program_counter_ = operations_[size_t(OperationsSlot::FetchDecodeExecute)];\
}\
op;\
cycles_in_phase_ = 0; \
}
#define bus_access() \
interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \
irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \
number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \
++cycles_in_phase_; \
nextBusOperation = BusOperation::None; \
if(number_of_cycles <= Cycles(0)) break;
@ -66,11 +57,13 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// Deal with a potential RDY state, if this 6502 has anything connected to ready.
while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) {
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
++cycles_in_phase_;
}
// Deal with a potential STP state, if this 6502 implements STP.
while(has_stpwai(personality) && stop_is_active_ && number_of_cycles > Cycles(0)) {
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
++cycles_in_phase_;
if(interrupt_requests_ & InterruptRequestFlags::Reset) {
stop_is_active_ = false;
checkSchedule();
@ -81,6 +74,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// Deal with a potential WAI state, if this 6502 implements WAI.
while(has_stpwai(personality) && wait_is_active_ && number_of_cycles > Cycles(0)) {
number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue);
++cycles_in_phase_;
interrupt_requests_ |= (irq_line_ & inverse_interrupt_flag_);
if(interrupt_requests_ & InterruptRequestFlags::NMI || irq_line_) {
wait_is_active_ = false;
@ -566,7 +560,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
#define BRA(condition) \
pc_.full++; \
if(condition) { \
scheduled_program_counter_ = do_branch; \
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoBRA)]; \
}
case OperationBPL: BRA(!(negative_result_&0x80)); continue;
@ -593,7 +587,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// 65C02 modification to all branches: a branch that is taken but requires only a single cycle
// to target its destination skips any pending interrupts.
// Cf. http://forum.6502.org/viewtopic.php?f=4&t=1634
scheduled_program_counter_ = fetch_decode_execute;
scheduled_program_counter_ = operations_[size_t(OperationsSlot::FetchDecodeExecute)];
}
continue;
@ -611,22 +605,9 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// and (iii) read from the corresponding zero page.
const uint8_t mask = uint8_t(1 << ((operation_ >> 4)&7));
if((operand_ & mask) == ((operation_ & 0x80) ? mask : 0)) {
static const MicroOp do_branch[] = {
CycleFetchOperand, // Fetch offset.
OperationIncrementPC,
CycleFetchFromHalfUpdatedPC,
OperationAddSignedOperandToPC16,
OperationMoveToNextProgram
};
scheduled_program_counter_ = do_branch;
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoBBRBBS)];
} else {
static const MicroOp do_not_branch[] = {
CycleFetchOperand,
OperationIncrementPC,
CycleFetchFromHalfUpdatedPC,
OperationMoveToNextProgram
};
scheduled_program_counter_ = do_not_branch;
scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoNotBBRBBS)];
}
} break;
@ -730,56 +711,6 @@ void ProcessorBase::set_nmi_line(bool active) {
nmi_line_is_enabled_ = active;
}
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() {
static const MicroOp reset[] = {
CycleFetchOperand,
CycleFetchOperand,
CycleNoWritePush,
CycleNoWritePush,
OperationRSTPickVector,
CycleNoWritePush,
OperationSetNMIRSTFlags,
CycleReadVectorLow,
CycleReadVectorHigh,
OperationMoveToNextProgram
};
return reset;
}
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() {
static const MicroOp reset[] = {
CycleFetchOperand,
CycleFetchOperand,
CyclePushPCH,
CyclePushPCL,
OperationBRKPickVector,
OperationSetOperandFromFlags,
CyclePushOperand,
OperationSetIRQFlags,
CycleReadVectorLow,
CycleReadVectorHigh,
OperationMoveToNextProgram
};
return reset;
}
inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() {
static const MicroOp reset[] = {
CycleFetchOperand,
CycleFetchOperand,
CyclePushPCH,
CyclePushPCL,
OperationNMIPickVector,
OperationSetOperandFromFlags,
CyclePushOperand,
OperationSetNMIRSTFlags,
CycleReadVectorLow,
CycleReadVectorHigh,
OperationMoveToNextProgram
};
return reset;
}
uint8_t ProcessorStorage::get_flags() {
return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_;
}

View File

@ -82,7 +82,7 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
decimal_flag_ &= Flag::Decimal;
overflow_flag_ &= Flag::Overflow;
const InstructionList operations_6502[256] = {
const InstructionList operations_6502[] = {
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh),
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
@ -218,8 +218,79 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
/* 0xfa NOP # */ ImpliedNop(), /* 0xfb INS abs, y */ AbsoluteYReadModifyWrite(OperationINS),
/* 0xfc NOP abs, x */ AbsoluteXNop(), /* 0xfd SBC abs, x */ AbsoluteXRead(OperationSBC),
/* 0xfe INC abs, x */ AbsoluteXReadModifyWrite(OperationINC), /* 0xff INS abs, x */ AbsoluteXReadModifyWrite(OperationINS),
/* 0x100: Fetch, decode, execute. */
{
CycleFetchOperation,
CycleFetchOperand,
OperationDecodeOperation
},
/* 0x101: Reset. */
Program(
CycleFetchOperand,
CycleFetchOperand,
CycleNoWritePush,
CycleNoWritePush,
OperationRSTPickVector,
CycleNoWritePush,
OperationSetNMIRSTFlags,
CycleReadVectorLow,
CycleReadVectorHigh
),
/* 0x102: IRQ. */
Program(
CycleFetchOperand,
CycleFetchOperand,
CyclePushPCH,
CyclePushPCL,
OperationBRKPickVector,
OperationSetOperandFromFlags,
CyclePushOperand,
OperationSetIRQFlags,
CycleReadVectorLow,
CycleReadVectorHigh
),
/* 0x103: NMI. */
Program(
CycleFetchOperand,
CycleFetchOperand,
CyclePushPCH,
CyclePushPCL,
OperationNMIPickVector,
OperationSetOperandFromFlags,
CyclePushOperand,
OperationSetNMIRSTFlags,
CycleReadVectorLow,
CycleReadVectorHigh
),
/* 0x104: Do BRA. */
Program(
CycleReadFromPC,
CycleAddSignedOperandToPC
),
/* 0x105: Do BBR or BBS. */
Program(
CycleFetchOperand, // Fetch offset.
OperationIncrementPC,
CycleFetchFromHalfUpdatedPC,
OperationAddSignedOperandToPC16
),
/* 0x106: Complete BBR or BBS without branching. */
Program(
CycleFetchOperand,
OperationIncrementPC,
CycleFetchFromHalfUpdatedPC
)
};
static_assert(sizeof(operations_6502) == sizeof(operations_));
// Install the basic 6502 table.
memcpy(operations_, operations_6502, sizeof(operations_));

View File

@ -24,7 +24,7 @@ class ProcessorStorage {
This micro-instruction set was put together in a fairly ad hoc fashion, I'm afraid, so is unlikely to be optimal.
*/
enum MicroOp {
enum MicroOp: uint8_t {
CycleFetchOperation, // fetches (PC) to operation_, storing PC to last_operation_pc_ before incrementing it
CycleFetchOperand, // 6502: fetches from (PC) to operand_; 65C02: as 6502 unless operation_ indicates a one-cycle NOP, in which case this is a no0op
OperationDecodeOperation, // schedules the microprogram associated with operation_
@ -197,10 +197,35 @@ class ProcessorStorage {
OperationScheduleStop, // puts the processor into STP mode (i.e. it'll do nothing until a reset is received)
};
using InstructionList = MicroOp[10];
InstructionList operations_[256];
using InstructionList = MicroOp[12];
/// Defines the locations in operations_ of various named microprograms; the first 256 entries
/// in operations_ are mapped directly from instruction codes and therefore not named.
enum class OperationsSlot {
/// Fetches the next operation, and its operand, then schedules the corresponding set of operations_.
/// [Caveat: the 65C02 adds single-cycle NOPs; this microprogram won't fetch an operand for those].
FetchDecodeExecute = 256,
/// Performs the 6502's reset sequence.
Reset,
/// Performs the 6502's IRQ sequence.
IRQ,
/// Performs the 6502's NMI sequence.
NMI,
/// Performs a branch, e.g. the entry for BCC will evaluate whether carry is clear and, if so, will jump
/// to this instruction list.
DoBRA,
/// On a 65c02,
DoBBRBBS,
DoNotBBRBBS,
Max
};
InstructionList operations_[size_t(OperationsSlot::Max)];
const MicroOp *scheduled_program_counter_ = nullptr;
int cycles_in_phase_ = 0;
/*
Storage for the 6502 registers; F is stored as individual flags.
@ -260,27 +285,6 @@ class ProcessorStorage {
uint8_t irq_line_ = 0, irq_request_history_ = 0;
bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false;
/*!
Gets the program representing an RST response.
@returns The program representing an RST response.
*/
inline const MicroOp *get_reset_program();
/*!
Gets the program representing an IRQ response.
@returns The program representing an IRQ response.
*/
inline const MicroOp *get_irq_program();
/*!
Gets the program representing an NMI response.
@returns The program representing an NMI response.
*/
inline const MicroOp *get_nmi_program();
};
#endif /* _502Storage_h */

View File

@ -9,6 +9,8 @@
#include "Struct.hpp"
#include <algorithm>
#include <iomanip>
#include <sstream>
// MARK: - Setters
@ -107,18 +109,84 @@ bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::s
// MARK: - Getters
template <typename Type> bool Reflection::get(Struct &target, const std::string &name, Type &value) {
return false;
}
template <> bool Reflection::get(Struct &target, const std::string &name, bool &value) {
template <typename Type> bool Reflection::get(const Struct &target, const std::string &name, Type &value) {
const auto target_type = target.type_of(name);
if(!target_type) return false;
if(*target_type == typeid(bool)) {
value = *reinterpret_cast<const bool *>(target.get(name));
if(*target_type == typeid(Type)) {
memcpy(&value, target.get(name), sizeof(Type));
return true;
}
return false;
}
template <typename Type> Type Reflection::get(const Struct &target, const std::string &name) {
Type value;
get(target, name, value);
return value;
}
// MARK: - Description
std::string Reflection::Struct::description() const {
std::ostringstream stream;
stream << "{";
bool is_first = true;
for(const auto &key: all_keys()) {
if(!is_first) stream << ", ";
is_first = false;
stream << key << ": ";
const auto type = type_of(key);
// Output Bools as yes/no.
if(*type == typeid(bool)) {
bool value;
::Reflection::get(*this, key, value);
stream << (value ? "true" : "false");
continue;
}
// Output Ints of all sizes as hex.
#define OutputIntC(int_type, cast_type) if(*type == typeid(int_type)) { stream << std::setfill('0') << std::setw(sizeof(int_type)*2) << std::hex << cast_type(::Reflection::get<int_type>(*this, key)); continue; }
#define OutputInt(int_type) OutputIntC(int_type, int_type)
OutputIntC(int8_t, int16_t);
OutputIntC(uint8_t, uint16_t);
OutputInt(int16_t);
OutputInt(uint16_t);
OutputInt(int32_t);
OutputInt(uint32_t);
OutputInt(int64_t);
OutputInt(uint64_t);
#undef OutputInt
// Output floats and strings natively.
#define OutputNative(val_type) if(*type == typeid(val_type)) { stream << ::Reflection::get<val_type>(*this, key); continue; }
OutputNative(float);
OutputNative(double);
OutputNative(char *);
OutputNative(std::string);
#undef OutputNAtive
// Output the current value of any enums.
if(!Enum::name(*type).empty()) {
const int value = ::Reflection::get<int>(*this, key);
stream << Enum::to_string(*type, value);
continue;
}
// Recurse to deal with embedded objects.
if(*type == typeid(Reflection::Struct)) {
const Reflection::Struct *const child = reinterpret_cast<const Reflection::Struct *>(get(key));
stream << child->description();
continue;
}
}
stream << "}";
return stream.str();
}

View File

@ -9,6 +9,7 @@
#ifndef Struct_hpp
#define Struct_hpp
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <string>
@ -24,12 +25,18 @@ namespace Reflection {
#define DeclareField(Name) declare(&Name, #Name)
struct Struct {
virtual std::vector<std::string> all_keys() = 0;
virtual const std::type_info *type_of(const std::string &name) = 0;
virtual std::vector<std::string> all_keys() const = 0;
virtual const std::type_info *type_of(const std::string &name) const = 0;
virtual void set(const std::string &name, const void *value) = 0;
virtual const void *get(const std::string &name) = 0;
virtual std::vector<std::string> values_for(const std::string &name) = 0;
virtual const void *get(const std::string &name) const = 0;
virtual std::vector<std::string> values_for(const std::string &name) const = 0;
virtual ~Struct() {}
/*!
@returns A string describing this struct. This string has no guaranteed layout, may not be
sufficiently formed for a formal language parser, etc.
*/
std::string description() const;
};
/*!
@ -86,9 +93,14 @@ bool fuzzy_set(Struct &target, const std::string &name, const std::string &value
@returns @c true if the property was successfully read; @c false otherwise.
*/
template <typename Type> bool get(Struct &target, const std::string &name, Type &value);
template <typename Type> bool get(const Struct &target, const std::string &name, Type &value);
template <> bool get(Struct &target, const std::string &name, bool &value);
/*!
Attempts to get the property @c name to @c value ; will perform limited type conversions.
@returns @c true if the property was successfully read; a default-constructed instance of Type otherwise.
*/
template <typename Type> Type get(const Struct &target, const std::string &name);
// TODO: move this elsewhere. It's just a sketch anyway.
@ -106,10 +118,10 @@ template <typename Owner> class StructImpl: public Struct {
@returns the value of type @c Type that is loaded from the offset registered for the field @c name.
It is the caller's responsibility to provide an appropriate type of data.
*/
const void *get(const std::string &name) final {
const void *get(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return reinterpret_cast<uint8_t *>(this) + iterator->second.offset;
return reinterpret_cast<const uint8_t *>(this) + iterator->second.offset;
}
/*!
@ -126,7 +138,7 @@ template <typename Owner> class StructImpl: public Struct {
/*!
@returns @c type_info for the field @c name.
*/
const std::type_info *type_of(const std::string &name) final {
const std::type_info *type_of(const std::string &name) const final {
const auto iterator = contents_.find(name);
if(iterator == contents_.end()) return nullptr;
return iterator->second.type;
@ -136,7 +148,7 @@ template <typename Owner> class StructImpl: public Struct {
@returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct;
the empty list otherwise.
*/
std::vector<std::string> values_for(const std::string &name) final {
std::vector<std::string> values_for(const std::string &name) const final {
std::vector<std::string> result;
// Return an empty vector if this field isn't declared.
@ -168,7 +180,7 @@ template <typename Owner> class StructImpl: public Struct {
/*!
@returns A vector of all declared fields for this struct.
*/
std::vector<std::string> all_keys() final {
std::vector<std::string> all_keys() const final {
std::vector<std::string> keys;
for(const auto &pair: contents_) {
keys.push_back(pair.first);
@ -190,14 +202,14 @@ template <typename Owner> class StructImpl: public Struct {
*/
/*!
Exposes the field pointed to by @c t for reflection as @c name.
Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct,
it'll be the struct that's exposed.
*/
template <typename Type> void declare(Type *t, const std::string &name) {
contents_.emplace(
std::make_pair(
name,
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type))
));
if constexpr (std::is_class<Type>()) {
if(declare_reflectable(t, name)) return;
}
declare_emplace(t, name);
}
/*!
@ -233,7 +245,7 @@ template <typename Owner> class StructImpl: public Struct {
@returns @c true if this subclass of @c Struct has not yet declared any fields.
*/
bool needs_declare() {
return !contents_.size();
return contents_.empty();
}
/*!
@ -256,6 +268,24 @@ template <typename Owner> class StructImpl: public Struct {
}
private:
template <typename Type> bool declare_reflectable(Type *t, const std::string &name) {
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
if(str) {
declare_emplace(str, name);
return true;
}
return false;
}
template <typename Type> void declare_emplace(Type *t, const std::string &name) {
contents_.emplace(
std::make_pair(
name,
Field(typeid(Type), reinterpret_cast<uint8_t *>(t) - reinterpret_cast<uint8_t *>(this), sizeof(Type))
));
}
struct Field {
const std::type_info *type;
ssize_t offset;

View File

@ -45,7 +45,7 @@ void Drive::set_rotation_speed(float revolutions_per_minute) {
// From there derive the appropriate rotational multiplier and possibly update the
// count of cycles since the index hole proportionally.
const float new_rotational_multiplier = float(cycles_per_revolution_) / float(get_input_clock_rate());
cycles_since_index_hole_ *= new_rotational_multiplier / rotational_multiplier_;
cycles_since_index_hole_ = Cycles::IntType(float(cycles_since_index_hole_) * new_rotational_multiplier / rotational_multiplier_);
rotational_multiplier_ = new_rotational_multiplier;
cycles_since_index_hole_ %= cycles_per_revolution_;
}