1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <cassert>
19#include <functional>
20
21#include "bspf.hxx"
22#include "Logger.hxx"
23
24#include "MediaFactory.hxx"
25#include "Sound.hxx"
26
27#ifdef CHEATCODE_SUPPORT
28 #include "CheatManager.hxx"
29#endif
30#ifdef DEBUGGER_SUPPORT
31 #include "Debugger.hxx"
32#endif
33#ifdef GUI_SUPPORT
34 #include "Menu.hxx"
35 #include "CommandMenu.hxx"
36 #include "Launcher.hxx"
37 #include "TimeMachine.hxx"
38 #include "Widget.hxx"
39#endif
40#ifdef SQLITE_SUPPORT
41 #include "KeyValueRepositorySqlite.hxx"
42 #include "SettingsDb.hxx"
43#endif
44
45#include "FSNode.hxx"
46#include "MD5.hxx"
47#include "Cart.hxx"
48#include "CartDetector.hxx"
49#include "FrameBuffer.hxx"
50#include "TIASurface.hxx"
51#include "TIAConstants.hxx"
52#include "Settings.hxx"
53#include "PropsSet.hxx"
54#include "EventHandler.hxx"
55#include "PNGLibrary.hxx"
56#include "Console.hxx"
57#include "Random.hxx"
58#include "StateManager.hxx"
59#include "TimerManager.hxx"
60#include "Version.hxx"
61#include "TIA.hxx"
62#include "DispatchResult.hxx"
63#include "EmulationWorker.hxx"
64#include "AudioSettings.hxx"
65#include "repository/KeyValueRepositoryNoop.hxx"
66#include "repository/KeyValueRepositoryConfigfile.hxx"
67#include "M6532.hxx"
68
69#include "OSystem.hxx"
70
71using namespace std::chrono;
72
73namespace {
74 constexpr uInt32 FPS_METER_QUEUE_SIZE = 100;
75}
76
77// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
78OSystem::OSystem()
79 : myLauncherUsed(false),
80 myQuitLoop(false),
81 mySettingsLoaded(false),
82 myFpsMeter(FPS_METER_QUEUE_SIZE)
83{
84 // Get built-in features
85 #ifdef SOUND_SUPPORT
86 myFeatures += "Sound ";
87 #endif
88 #ifdef JOYSTICK_SUPPORT
89 myFeatures += "Joystick ";
90 #endif
91 #ifdef DEBUGGER_SUPPORT
92 myFeatures += "Debugger ";
93 #endif
94 #ifdef CHEATCODE_SUPPORT
95 myFeatures += "Cheats ";
96 #endif
97 #ifdef PNG_SUPPORT
98 myFeatures += "PNG ";
99 #endif
100 #ifdef ZIP_SUPPORT
101 myFeatures += "ZIP";
102 #endif
103
104 // Get build info
105 ostringstream info;
106
107 info << "Build " << STELLA_BUILD << ", using " << MediaFactory::backendName()
108 << " [" << BSPF::ARCH << "]";
109 myBuildInfo = info.str();
110
111 mySettings = MediaFactory::createSettings();
112
113 myPropSet = make_unique<PropertiesSet>();
114
115 Logger::instance().setLogCallback(
116 std::bind(&OSystem::logMessage, this, std::placeholders::_1, std::placeholders::_2)
117 );
118}
119
120// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
121OSystem::~OSystem()
122{
123 Logger::instance().clearLogCallback();
124}
125
126// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
127bool OSystem::create()
128{
129 ostringstream buf;
130 buf << "Stella " << STELLA_VERSION << endl
131 << " Features: " << myFeatures << endl
132 << " " << myBuildInfo << endl << endl
133 << "Base directory: '"
134 << FilesystemNode(myBaseDir).getShortPath() << "'" << endl
135 << "State directory: '"
136 << FilesystemNode(myStateDir).getShortPath() << "'" << endl
137 << "NVRam directory: '"
138 << FilesystemNode(myNVRamDir).getShortPath() << "'" << endl;
139
140 if(!myConfigFile.empty())
141 buf << "Configuration file: '"
142 << FilesystemNode(myConfigFile).getShortPath() << "'" << endl;
143
144 buf << "Game properties: '"
145 << FilesystemNode(myPropertiesFile).getShortPath() << "'" << endl
146 << "Cheat file: '"
147 << FilesystemNode(myCheatFile).getShortPath() << "'" << endl
148 << "Palette file: '"
149 << FilesystemNode(myPaletteFile).getShortPath() << "'" << endl;
150 Logger::info(buf.str());
151
152 // NOTE: The framebuffer MUST be created before any other object!!!
153 // Get relevant information about the video hardware
154 // This must be done before any graphics context is created, since
155 // it may be needed to initialize the size of graphical objects
156 try { myFrameBuffer = MediaFactory::createVideo(*this); }
157 catch(...) { return false; }
158 if(!myFrameBuffer->initialize())
159 return false;
160
161 // Create the event handler for the system
162 myEventHandler = MediaFactory::createEventHandler(*this);
163 myEventHandler->initialize();
164
165 myStateManager = make_unique<StateManager>(*this);
166 myTimerManager = make_unique<TimerManager>();
167 myAudioSettings = make_unique<AudioSettings>(*mySettings);
168
169 // Create the sound object; the sound subsystem isn't actually
170 // opened until needed, so this is non-blocking (on those systems
171 // that only have a single sound device (no hardware mixing))
172 createSound();
173
174 // Create random number generator
175 myRandom = make_unique<Random>(uInt32(TimerManager::getTicks()));
176
177#ifdef CHEATCODE_SUPPORT
178 myCheatManager = make_unique<CheatManager>(*this);
179 myCheatManager->loadCheatDatabase();
180#endif
181
182#ifdef GUI_SUPPORT
183 // Create various subsystems (menu and launcher GUI objects, etc)
184 myMenu = make_unique<Menu>(*this);
185 myCommandMenu = make_unique<CommandMenu>(*this);
186 myTimeMachine = make_unique<TimeMachine>(*this);
187 myLauncher = make_unique<Launcher>(*this);
188#endif
189
190#ifdef PNG_SUPPORT
191 // Create PNG handler
192 myPNGLib = make_unique<PNGLibrary>(*this);
193#endif
194
195 myPropSet->load(myPropertiesFile);
196
197 return true;
198}
199
200// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201void OSystem::loadConfig(const Settings::Options& options)
202{
203 // Get base directory and config file from derived class
204 // It will decide whether it can override its default location
205 getBaseDirAndConfig(myBaseDir, myConfigFile,
206 myDefaultSaveDir, myDefaultLoadDir,
207 ourOverrideBaseDirWithApp, ourOverrideBaseDir);
208
209 // Get fully-qualified pathnames, and make directories when needed
210 FilesystemNode node(myBaseDir);
211 if(!node.isDirectory())
212 node.makeDir();
213 myBaseDir = node.getPath();
214 if(!myConfigFile.empty())
215 myConfigFile = FilesystemNode(myConfigFile).getPath();
216
217 FilesystemNode save(myDefaultSaveDir);
218 if(!save.isDirectory())
219 save.makeDir();
220 myDefaultSaveDir = save.getShortPath();
221
222 FilesystemNode load(myDefaultLoadDir);
223 if(!load.isDirectory())
224 load.makeDir();
225 myDefaultLoadDir = load.getShortPath();
226
227#ifdef SQLITE_SUPPORT
228 mySettingsDb = make_shared<SettingsDb>(myBaseDir, "settings");
229 if(!mySettingsDb->initialize())
230 mySettingsDb.reset();
231#endif
232
233 mySettings->setRepository(createSettingsRepository());
234
235 Logger::debug("Loading config options ...");
236 mySettings->load(options);
237 mySettingsLoaded = true;
238
239 // Get updated paths for all configuration files
240 setConfigPaths();
241}
242
243// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244void OSystem::saveConfig()
245{
246 // Ask all subsystems to save their settings
247 if(myFrameBuffer)
248 {
249 // Save the last windowed position and display on system shutdown
250 myFrameBuffer->updateWindowedPos();
251 settings().setValue("display", myFrameBuffer->getCurrentDisplayIndex());
252
253 Logger::debug("Saving TV effects options ...");
254 myFrameBuffer->tiaSurface().ntsc().saveConfig(settings());
255 }
256
257 Logger::debug("Saving config options ...");
258 mySettings->save();
259
260 if(myPropSet && myPropSet->save(myPropertiesFile))
261 Logger::debug("Saving properties set ...");
262}
263
264// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
265void OSystem::setConfigPaths()
266{
267 // Make sure all required directories actually exist
268 auto buildDirIfRequired = [](string& path, const string& pathToBuild)
269 {
270 FilesystemNode node(pathToBuild);
271 if(!node.isDirectory())
272 node.makeDir();
273
274 path = node.getPath();
275 };
276
277 buildDirIfRequired(myStateDir, myBaseDir + "state");
278 buildDirIfRequired(myNVRamDir, myBaseDir + "nvram");
279
280#ifdef PNG_SUPPORT
281 mySnapshotSaveDir = mySettings->getString("snapsavedir");
282 if(mySnapshotSaveDir == "") mySnapshotSaveDir = defaultSaveDir();
283 buildDirIfRequired(mySnapshotSaveDir, mySnapshotSaveDir);
284
285 mySnapshotLoadDir = mySettings->getString("snaploaddir");
286 if(mySnapshotLoadDir == "") mySnapshotLoadDir = defaultLoadDir();
287 buildDirIfRequired(mySnapshotLoadDir, mySnapshotLoadDir);
288#endif
289
290 myCheatFile = FilesystemNode(myBaseDir + "stella.cht").getPath();
291 myPaletteFile = FilesystemNode(myBaseDir + "stella.pal").getPath();
292 myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro").getPath();
293
294#if 0
295 // Debug code
296 auto dbgPath = [](const string& desc, const string& location)
297 {
298 cerr << desc << ": " << location << endl;
299 };
300 dbgPath("base dir ", myBaseDir);
301 dbgPath("state dir ", myStateDir);
302 dbgPath("nvram dir ", myNVRamDir);
303 dbgPath("ssave dir ", mySnapshotSaveDir);
304 dbgPath("sload dir ", mySnapshotLoadDir);
305 dbgPath("cheat file", myCheatFile);
306 dbgPath("pal file ", myPaletteFile);
307 dbgPath("pro file ", myPropertiesFile);
308 dbgPath("INI file ", myConfigFile);
309#endif
310}
311
312// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
313FBInitStatus OSystem::createFrameBuffer()
314{
315 // Re-initialize the framebuffer to current settings
316 FBInitStatus fbstatus = FBInitStatus::FailComplete;
317 switch(myEventHandler->state())
318 {
319 case EventHandlerState::EMULATION:
320 case EventHandlerState::PAUSE:
321 #ifdef GUI_SUPPORT
322 case EventHandlerState::OPTIONSMENU:
323 case EventHandlerState::CMDMENU:
324 case EventHandlerState::TIMEMACHINE:
325 #endif
326 if((fbstatus = myConsole->initializeVideo()) != FBInitStatus::Success)
327 return fbstatus;
328 break;
329
330 #ifdef GUI_SUPPORT
331 case EventHandlerState::LAUNCHER:
332 if((fbstatus = myLauncher->initializeVideo()) != FBInitStatus::Success)
333 return fbstatus;
334 break;
335 #endif
336
337 #ifdef DEBUGGER_SUPPORT
338 case EventHandlerState::DEBUGGER:
339 if((fbstatus = myDebugger->initializeVideo()) != FBInitStatus::Success)
340 return fbstatus;
341 break;
342 #endif
343
344 case EventHandlerState::NONE: // Should never happen
345 default:
346 Logger::error("ERROR: Unknown emulation state in createFrameBuffer()");
347 break;
348 }
349 return fbstatus;
350}
351
352// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
353void OSystem::createSound()
354{
355 if(!mySound)
356 mySound = MediaFactory::createAudio(*this, *myAudioSettings);
357#ifndef SOUND_SUPPORT
358 myAudioSettings->setEnabled(false);
359#endif
360}
361
362// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
363string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
364 bool newrom)
365{
366 bool showmessage = false;
367
368 // If same ROM has been given, we reload the current one (assuming one exists)
369 if(!newrom && rom == myRomFile)
370 {
371 showmessage = true; // we show a message if a ROM is being reloaded
372 }
373 else
374 {
375 myRomFile = rom;
376 myRomMD5 = md5sum;
377
378 // Each time a new console is loaded, we simulate a cart removal
379 // Some carts need knowledge of this, as they behave differently
380 // based on how many power-cycles they've been through since plugged in
381 mySettings->setValue("romloadcount", 0);
382 }
383
384 // Create an instance of the 2600 game console
385 ostringstream buf;
386
387 myEventHandler->handleConsoleStartupEvents();
388
389 try
390 {
391 closeConsole();
392 myConsole = openConsole(myRomFile, myRomMD5);
393 }
394 catch(const runtime_error& e)
395 {
396 buf << "ERROR: Couldn't create console (" << e.what() << ")";
397 Logger::error(buf.str());
398 return buf.str();
399 }
400
401 if(myConsole)
402 {
403 #ifdef DEBUGGER_SUPPORT
404 myDebugger = make_unique<Debugger>(*this, *myConsole);
405 myDebugger->initialize();
406 myConsole->attachDebugger(*myDebugger);
407 #endif
408 #ifdef CHEATCODE_SUPPORT
409 myCheatManager->loadCheats(myRomMD5);
410 #endif
411 myEventHandler->reset(EventHandlerState::EMULATION);
412 myEventHandler->setMouseControllerMode(mySettings->getString("usemouse"));
413 if(createFrameBuffer() != FBInitStatus::Success) // Takes care of initializeVideo()
414 {
415 Logger::error("ERROR: Couldn't create framebuffer for console");
416 myEventHandler->reset(EventHandlerState::LAUNCHER);
417 return "ERROR: Couldn't create framebuffer for console";
418 }
419 myConsole->initializeAudio();
420
421 if(showmessage)
422 {
423 const string& id = myConsole->cartridge().multiCartID();
424 if(id == "")
425 myFrameBuffer->showMessage("New console created");
426 else
427 myFrameBuffer->showMessage("Multicart " +
428 myConsole->cartridge().detectedType() + ", loading ROM" + id);
429 }
430 buf << "Game console created:" << endl
431 << " ROM file: " << myRomFile.getShortPath() << endl << endl
432 << getROMInfo(*myConsole);
433 Logger::info(buf.str());
434
435 myFrameBuffer->setCursorState();
436
437 myEventHandler->handleConsoleStartupEvents();
438 myConsole->riot().update();
439
440 #ifdef DEBUGGER_SUPPORT
441 if(mySettings->getBool("debug"))
442 myEventHandler->enterDebugMode();
443 #endif
444 }
445 return EmptyString;
446}
447
448// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
449bool OSystem::reloadConsole()
450{
451 return createConsole(myRomFile, myRomMD5, false) == EmptyString;
452}
453
454// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
455bool OSystem::hasConsole() const
456{
457 return myConsole != nullptr &&
458 myEventHandler->state() != EventHandlerState::LAUNCHER;
459}
460
461// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
462bool OSystem::createLauncher(const string& startdir)
463{
464 closeConsole();
465
466 if(mySound)
467 mySound->close();
468
469 mySettings->setValue("tmpromdir", startdir);
470 bool status = false;
471
472#ifdef GUI_SUPPORT
473 myEventHandler->reset(EventHandlerState::LAUNCHER);
474 if(createFrameBuffer() == FBInitStatus::Success)
475 {
476 myLauncher->reStack();
477 myFrameBuffer->setCursorState();
478
479 status = true;
480 }
481 else
482 Logger::error("ERROR: Couldn't create launcher");
483#endif
484
485 myLauncherUsed = myLauncherUsed || status;
486 return status;
487}
488
489// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
490string OSystem::getROMInfo(const FilesystemNode& romfile)
491{
492 unique_ptr<Console> console;
493 try
494 {
495 string md5;
496 console = openConsole(romfile, md5);
497 }
498 catch(const runtime_error& e)
499 {
500 ostringstream buf;
501 buf << "ERROR: Couldn't get ROM info (" << e.what() << ")";
502 return buf.str();
503 }
504
505 return getROMInfo(*console);
506}
507
508// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
509void OSystem::logMessage(const string& message, Logger::Level level)
510{
511 if(level == Logger::Level::ERR)
512 {
513 cout << message << endl << std::flush;
514 myLogMessages += message + "\n";
515 }
516 else if(int(level) <= uInt8(mySettings->getInt("loglevel")) || !mySettingsLoaded)
517 {
518 if(mySettings->getBool("logtoconsole"))
519 cout << message << endl << std::flush;
520 myLogMessages += message + "\n";
521 }
522}
523
524// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
525void OSystem::resetFps()
526{
527 myFpsMeter.reset();
528}
529
530// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
531unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string& md5)
532{
533 unique_ptr<Console> console;
534
535 // Open the cartridge image and read it in
536 ByteBuffer image;
537 uInt32 size = 0;
538 if((image = openROM(romfile, md5, size)) != nullptr)
539 {
540 // Get a valid set of properties, including any entered on the commandline
541 // For initial creation of the Cart, we're only concerned with the BS type
542 Properties props;
543 myPropSet->getMD5(md5, props);
544
545 // Local helper method
546 auto CMDLINE_PROPS_UPDATE = [&](const string& name, PropType prop)
547 {
548 const string& s = mySettings->getString(name);
549 if(s != "") props.set(prop, s);
550 };
551
552 CMDLINE_PROPS_UPDATE("bs", PropType::Cart_Type);
553 CMDLINE_PROPS_UPDATE("type", PropType::Cart_Type);
554 CMDLINE_PROPS_UPDATE("startbank", PropType::Cart_StartBank);
555
556 // Now create the cartridge
557 string cartmd5 = md5;
558 const string& type = props.get(PropType::Cart_Type);
559 unique_ptr<Cartridge> cart =
560 CartDetector::create(romfile, image, size, cartmd5, type, *mySettings);
561
562 // It's possible that the cart created was from a piece of the image,
563 // and that the md5 (and hence the cart) has changed
564 if(props.get(PropType::Cart_MD5) != cartmd5)
565 {
566 if(!myPropSet->getMD5(cartmd5, props))
567 {
568 // Cart md5 wasn't found, so we create a new props for it
569 props.set(PropType::Cart_MD5, cartmd5);
570 props.set(PropType::Cart_Name, props.get(PropType::Cart_Name)+cart->multiCartID());
571 myPropSet->insert(props, false);
572 }
573 }
574
575 CMDLINE_PROPS_UPDATE("sp", PropType::Console_SwapPorts);
576 CMDLINE_PROPS_UPDATE("lc", PropType::Controller_Left);
577 CMDLINE_PROPS_UPDATE("rc", PropType::Controller_Right);
578 const string& s = mySettings->getString("bc");
579 if(s != "") {
580 props.set(PropType::Controller_Left, s);
581 props.set(PropType::Controller_Right, s);
582 }
583 CMDLINE_PROPS_UPDATE("cp", PropType::Controller_SwapPaddles);
584 CMDLINE_PROPS_UPDATE("ma", PropType::Controller_MouseAxis);
585 CMDLINE_PROPS_UPDATE("channels", PropType::Cart_Sound);
586 CMDLINE_PROPS_UPDATE("ld", PropType::Console_LeftDiff);
587 CMDLINE_PROPS_UPDATE("rd", PropType::Console_RightDiff);
588 CMDLINE_PROPS_UPDATE("tv", PropType::Console_TVType);
589 CMDLINE_PROPS_UPDATE("format", PropType::Display_Format);
590 CMDLINE_PROPS_UPDATE("ystart", PropType::Display_YStart);
591 CMDLINE_PROPS_UPDATE("pp", PropType::Display_Phosphor);
592 CMDLINE_PROPS_UPDATE("ppblend", PropType::Display_PPBlend);
593
594 // Finally, create the cart with the correct properties
595 if(cart)
596 console = make_unique<Console>(*this, cart, props, *myAudioSettings);
597 }
598
599 return console;
600}
601
602// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
603void OSystem::closeConsole()
604{
605 if(myConsole)
606 {
607 #ifdef CHEATCODE_SUPPORT
608 // If a previous console existed, save cheats before creating a new one
609 myCheatManager->saveCheats(myConsole->properties().get(PropType::Cart_MD5));
610 #endif
611 myConsole.reset();
612 }
613}
614
615// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
616ByteBuffer OSystem::openROM(const FilesystemNode& rom, string& md5, uInt32& size)
617{
618 // This method has a documented side-effect:
619 // It not only loads a ROM and creates an array with its contents,
620 // but also adds a properties entry if the one for the ROM doesn't
621 // contain a valid name
622
623 ByteBuffer image;
624 if((size = rom.read(image)) == 0)
625 return nullptr;
626
627 // If we get to this point, we know we have a valid file to open
628 // Now we make sure that the file has a valid properties entry
629 // To save time, only generate an MD5 if we really need one
630 if(md5 == "")
631 md5 = MD5::hash(image, size);
632
633 // Some games may not have a name, since there may not
634 // be an entry in stella.pro. In that case, we use the rom name
635 // and reinsert the properties object
636 Properties props;
637 myPropSet->getMD5WithInsert(rom, md5, props);
638
639 return image;
640}
641
642// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
643string OSystem::getROMInfo(const Console& console)
644{
645 const ConsoleInfo& info = console.about();
646 ostringstream buf;
647
648 buf << " Cart Name: " << info.CartName << endl
649 << " Cart MD5: " << info.CartMD5 << endl
650 << " Controller 0: " << info.Control0 << endl
651 << " Controller 1: " << info.Control1 << endl
652 << " Display Format: " << info.DisplayFormat << endl
653 << " Bankswitch Type: " << info.BankSwitch << endl;
654
655 return buf.str();
656}
657
658// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
659float OSystem::frameRate() const
660{
661 return myConsole ? myConsole->getFramerate() : 0;
662}
663
664// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
665double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
666{
667 if (!myConsole) return 0.;
668
669 TIA& tia(myConsole->tia());
670 EmulationTiming& timing(myConsole->emulationTiming());
671 DispatchResult dispatchResult;
672
673 // Check whether we have a frame pending for rendering...
674 bool framePending = tia.newFramePending();
675 // ... and copy it to the frame buffer. It is important to do this before
676 // the worker is started to avoid racing.
677 if (framePending) {
678 myFpsMeter.render(tia.framesSinceLastRender());
679 tia.renderToFrameBuffer();
680 }
681
682 // Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time
683 // and will run until we stop the worker.
684 emulationWorker.start(
685 timing.cyclesPerSecond(),
686 timing.maxCyclesPerTimeslice(),
687 timing.minCyclesPerTimeslice(),
688 &dispatchResult,
689 &tia
690 );
691
692 // Render the frame. This may block, but emulation will continue to run on the worker, so the
693 // audio pipeline is kept fed :)
694 if (framePending) myFrameBuffer->updateInEmulationMode(myFpsMeter.fps());
695
696 // Stop the worker and wait until it has finished
697 uInt64 totalCycles = emulationWorker.stop();
698
699 // Handle the dispatch result
700 switch (dispatchResult.getStatus()) {
701 case DispatchResult::Status::ok:
702 break;
703
704 case DispatchResult::Status::debugger:
705 #ifdef DEBUGGER_SUPPORT
706 myDebugger->start(
707 dispatchResult.getMessage(),
708 dispatchResult.getAddress(),
709 dispatchResult.wasReadTrap()
710 );
711 #endif
712
713 break;
714
715 case DispatchResult::Status::fatal:
716 #ifdef DEBUGGER_SUPPORT
717 myDebugger->startWithFatalError(dispatchResult.getMessage());
718 #else
719 cerr << dispatchResult.getMessage() << endl;
720 #endif
721 break;
722
723 default:
724 throw runtime_error("invalid emulation dispatch result");
725 }
726
727 // Handle frying
728 if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
729 myConsole->fry();
730
731 // Return the 6507 time used in seconds
732 return static_cast<double>(totalCycles) / static_cast<double>(timing.cyclesPerSecond());
733}
734
735// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
736void OSystem::mainLoop()
737{
738 // 6507 time
739 time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
740 // The emulation worker
741 EmulationWorker emulationWorker;
742
743 myFpsMeter.reset(TIAConstants::initialGarbageFrames);
744
745 for(;;)
746 {
747 bool wasEmulation = myEventHandler->state() == EventHandlerState::EMULATION;
748
749 myEventHandler->poll(TimerManager::getTicks());
750 if(myQuitLoop) break; // Exit if the user wants to quit
751
752 if (!wasEmulation && myEventHandler->state() == EventHandlerState::EMULATION) {
753 myFpsMeter.reset();
754 virtualTime = high_resolution_clock::now();
755 }
756
757 double timesliceSeconds;
758
759 if (myEventHandler->state() == EventHandlerState::EMULATION)
760 // Dispatch emulation and render frame (if applicable)
761 timesliceSeconds = dispatchEmulation(emulationWorker);
762 else {
763 // Render the GUI with 60 Hz in all other modes
764 timesliceSeconds = 1. / 60.;
765 myFrameBuffer->update();
766 }
767
768 duration<double> timeslice(timesliceSeconds);
769 virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
770 time_point<high_resolution_clock> now = high_resolution_clock::now();
771
772 // We allow 6507 time to lag behind by one frame max
773 double maxLag = myConsole
774 ? (
775 static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
776 static_cast<double>(myConsole->emulationTiming().cyclesPerSecond())
777 )
778 : 0;
779
780 if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
781 // If 6507 time is lagging behind more than one frame we reset it to real time
782 virtualTime = now;
783 else if (virtualTime > now) {
784 // Wait until we have caught up with 6507 time
785 std::this_thread::sleep_until(virtualTime);
786 }
787 }
788
789 // Cleanup time
790#ifdef CHEATCODE_SUPPORT
791 if(myConsole)
792 myCheatManager->saveCheats(myConsole->properties().get(PropType::Cart_MD5));
793
794 myCheatManager->saveCheatDatabase();
795#endif
796}
797
798// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
799shared_ptr<KeyValueRepository> OSystem::createSettingsRepository()
800{
801 #ifdef SQLITE_SUPPORT
802 return mySettingsDb
803 ? shared_ptr<KeyValueRepository>(mySettingsDb, &mySettingsDb->settingsRepository())
804 : make_shared<KeyValueRepositoryNoop>();
805 #else
806 if (myConfigFile.empty())
807 return make_shared<KeyValueRepositoryNoop>();
808
809 return make_shared<KeyValueRepositoryConfigfile>(myConfigFile);
810 #endif
811}
812
813// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
814string OSystem::ourOverrideBaseDir = "";
815bool OSystem::ourOverrideBaseDirWithApp = false;
816