diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 655cd5a67..92e96470f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -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 diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp index 6688debba..55bf935ba 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp @@ -30,7 +30,7 @@ class MultiStruct: public Reflection::Struct { } } - std::vector all_keys() final { + std::vector all_keys() const final { std::set keys; for(auto &options: options_) { const auto new_keys = options->all_keys(); @@ -39,7 +39,7 @@ class MultiStruct: public Reflection::Struct { return std::vector(keys.begin(), keys.end()); } - std::vector values_for(const std::string &name) final { + std::vector values_for(const std::string &name) const final { std::set 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(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; diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index 2dac65439..77192a93d 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -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 { + /*! + Provides the current state of the well-known, published internal registers. + */ + struct Registers: public Reflection::StructImpl { + 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 { + 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 { + 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. diff --git a/Processors/6502/Implementation/6502Base.cpp b/Processors/6502/Implementation/6502Base.cpp index 7de1bc331..f82294b72 100644 --- a/Processors/6502/Implementation/6502Base.cpp +++ b/Processors/6502/Implementation/6502Base.cpp @@ -8,6 +8,8 @@ #include "../6502.hpp" +#include + 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(value); break; case Register::X: x_ = static_cast(value); break; case Register::Y: y_ = static_cast(value); break; - case Register::S: s_ = static_cast(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]; +} diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index 9e8c04984..03706c04f 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -13,17 +13,7 @@ */ template void Processor::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 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();\ - } else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ - interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ - scheduled_program_counter_ = get_nmi_program();\ - } else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ - scheduled_program_counter_ = get_irq_program();\ - } \ - } else {\ - scheduled_program_counter_ = fetch_decode_execute;\ - }\ - op;\ + if(interrupt_requests_) {\ + if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\ + interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\ + scheduled_program_counter_ = operations_[size_t(OperationsSlot::Reset)];\ + } else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ + interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ + scheduled_program_counter_ = operations_[size_t(OperationsSlot::NMI)];\ + } else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ + scheduled_program_counter_ = operations_[size_t(OperationsSlot::IRQ)];\ + } \ + } else {\ + scheduled_program_counter_ = operations_[size_t(OperationsSlot::FetchDecodeExecute)];\ + }\ + 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 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 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 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 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 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_; } diff --git a/Processors/6502/Implementation/6502Storage.cpp b/Processors/6502/Implementation/6502Storage.cpp index e2d365272..e87d6140a 100644 --- a/Processors/6502/Implementation/6502Storage.cpp +++ b/Processors/6502/Implementation/6502Storage.cpp @@ -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_)); diff --git a/Processors/6502/Implementation/6502Storage.hpp b/Processors/6502/Implementation/6502Storage.hpp index bc06c0e2c..89801bd4f 100644 --- a/Processors/6502/Implementation/6502Storage.hpp +++ b/Processors/6502/Implementation/6502Storage.hpp @@ -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 */ diff --git a/Reflection/Struct.cpp b/Reflection/Struct.cpp index 19096e0ad..5567172d8 100644 --- a/Reflection/Struct.cpp +++ b/Reflection/Struct.cpp @@ -9,6 +9,8 @@ #include "Struct.hpp" #include +#include +#include // MARK: - Setters @@ -107,18 +109,84 @@ bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::s // MARK: - Getters -template 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 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(target.get(name)); + if(*target_type == typeid(Type)) { + memcpy(&value, target.get(name), sizeof(Type)); return true; } return false; } + +template 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(*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(*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(*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(get(key)); + stream << child->description(); + continue; + } + } + + stream << "}"; + + return stream.str(); +} diff --git a/Reflection/Struct.hpp b/Reflection/Struct.hpp index e6423deae..c51758d8a 100644 --- a/Reflection/Struct.hpp +++ b/Reflection/Struct.hpp @@ -9,6 +9,7 @@ #ifndef Struct_hpp #define Struct_hpp +#include #include #include #include @@ -24,12 +25,18 @@ namespace Reflection { #define DeclareField(Name) declare(&Name, #Name) struct Struct { - virtual std::vector all_keys() = 0; - virtual const std::type_info *type_of(const std::string &name) = 0; + virtual std::vector 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 values_for(const std::string &name) = 0; + virtual const void *get(const std::string &name) const = 0; + virtual std::vector 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 bool get(Struct &target, const std::string &name, Type &value); +template 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 Type get(const Struct &target, const std::string &name); // TODO: move this elsewhere. It's just a sketch anyway. @@ -106,10 +118,10 @@ template 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(this) + iterator->second.offset; + return reinterpret_cast(this) + iterator->second.offset; } /*! @@ -126,7 +138,7 @@ template 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 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 values_for(const std::string &name) final { + std::vector values_for(const std::string &name) const final { std::vector result; // Return an empty vector if this field isn't declared. @@ -168,7 +180,7 @@ template class StructImpl: public Struct { /*! @returns A vector of all declared fields for this struct. */ - std::vector all_keys() final { + std::vector all_keys() const final { std::vector keys; for(const auto &pair: contents_) { keys.push_back(pair.first); @@ -190,14 +202,14 @@ template 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 void declare(Type *t, const std::string &name) { - contents_.emplace( - std::make_pair( - name, - Field(typeid(Type), reinterpret_cast(t) - reinterpret_cast(this), sizeof(Type)) - )); + if constexpr (std::is_class()) { + if(declare_reflectable(t, name)) return; + } + declare_emplace(t, name); } /*! @@ -233,7 +245,7 @@ template 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 class StructImpl: public Struct { } private: + template bool declare_reflectable(Type *t, const std::string &name) { + Reflection::Struct *const str = static_cast(t); + if(str) { + declare_emplace(str, name); + return true; + } + + return false; + } + + template void declare_emplace(Type *t, const std::string &name) { + contents_.emplace( + std::make_pair( + name, + Field(typeid(Type), reinterpret_cast(t) - reinterpret_cast(this), sizeof(Type)) + )); + } + struct Field { const std::type_info *type; ssize_t offset; diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index b79f87532..e8a5a461f 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -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_; }