diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 60fda9a7d..eb9433c0c 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -38,7 +38,8 @@ class ConcreteMachine: public CPU::MOS6502::BusHandler, public Inputs::Keyboard, public AppleII::Machine, - public Activity::Source { + public Activity::Source, + public AppleII::Card::Delegate { private: struct VideoBusHandler : public AppleII::Video::BusHandler { public: @@ -66,8 +67,8 @@ class ConcreteMachine: speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); } void update_cards() { - for(const auto &card : cards_) { - if(card) card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); + for(const auto &card : just_in_time_cards_) { + card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); } cycles_since_card_update_ = 0; stretched_cycles_since_card_update_ = 0; @@ -84,10 +85,42 @@ class ConcreteMachine: Cycles cycles_since_audio_update_; ROMMachine::ROMFetcher rom_fetcher_; + + // MARK: - Cards std::array, 7> cards_; Cycles cycles_since_card_update_; + std::vector every_cycle_cards_; + std::vector just_in_time_cards_; + int stretched_cycles_since_card_update_ = 0; + void install_card(std::size_t slot, AppleII::Card *card) { + assert(slot >= 1 && slot < 8); + cards_[slot - 1].reset(card); + card->set_delegate(this); + pick_card_messaging_group(card); + } + + bool is_every_cycle_card(AppleII::Card *card) { + return !card->get_select_constraints(); + } + + void pick_card_messaging_group(AppleII::Card *card) { + const bool is_every_cycle = is_every_cycle_card(card); + std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; + std::vector &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; + + if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; + auto old_membership = std::find(undesired.begin(), undesired.end(), card); + if(old_membership != undesired.end()) undesired.erase(old_membership); + intended.push_back(card); + } + + void card_did_change_select_constraints(AppleII::Card *card) override { + pick_card_messaging_group(card); + } + + // MARK: - Memory Map struct MemoryBlock { uint8_t *read_pointer = nullptr; uint8_t *write_pointer = nullptr; @@ -174,6 +207,19 @@ class ConcreteMachine: ++ cycles_since_card_update_; cycles_since_audio_update_ += Cycles(7); + // The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched + // by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after + // 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary + // signal approximation that produces colour needs to be in phase, so a stretch of exactly + // 0.5 further colour cycles is added. The video class handles that implicitly, but it + // needs to be accumulated here for the audio. + cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65; + const bool is_stretched_cycle = !cycles_into_current_line_; + if(is_stretched_cycle) { + ++ cycles_since_audio_update_; + ++ stretched_cycles_since_card_update_; + } + /* There are five distinct zones of memory on an Apple II: @@ -286,7 +332,13 @@ class ConcreteMachine: break; } + /* + Communication with cards. + */ + if(address >= 0xc090 && address < 0xc800) { + // If this is a card access, figure out which card is at play before determining + // the totality of who needs messaging. size_t card_number = 0; AppleII::Card::Select select = AppleII::Card::None; @@ -306,25 +358,31 @@ class ConcreteMachine: select = AppleII::Card::IO; } - if(cards_[card_number]) { + // If the selected card is a just-in-time card, update the just-in-time cards, + // and then message it specifically. + AppleII::Card *const target = cards_[card_number].get(); + if(target && !is_every_cycle_card(target)) { update_cards(); - cards_[card_number]->perform_bus_operation(select, isReadOperation(operation), address, value); + target->perform_bus_operation(select, isReadOperation(operation), address, value); + } + + // Update all the every-cycle cards regardless, but send them a ::None select if they're + // not the one actually selected. + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation( + (card == target) ? select : AppleII::Card::None, + isReadOperation(operation), address, value); + } + } else { + // Update all every-cycle cards and give them the cycle. + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation(AppleII::Card::None, isReadOperation(operation), address, value); } } } - // The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched - // by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after - // 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary - // signal approximation that produces colour needs to be in phase, so a stretch of exactly - // 0.5 further colour cycles is added. The video class handles that implicitly, but it - // needs to be accumulated here for the audio. - cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65; - if(!cycles_into_current_line_) { - ++ cycles_since_audio_update_; - ++ stretched_cycles_since_card_update_; - } - return Cycles(1); } @@ -395,7 +453,8 @@ class ConcreteMachine: auto *const apple_target = dynamic_cast(target); if(apple_target->disk_controller != Target::DiskController::None) { - cards_[5].reset(new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector)); + // Apple recommended slot 6 for the (first) Disk II. + install_card(6, new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector)); } rom_ = (apple_target->model == Target::Model::II) ? apple2_rom_ : apple2plus_rom_;