private static void GUI_OnTick(GUITickEventArgs eventArgs) { _keys = eventArgs.Keys; if (_renderFrameNextTick) { eventArgs.FrameBuffer = _frameBuffer; eventArgs.ShouldRender = true; _renderFrameNextTick = false; } if (_playSoundNextTick) { eventArgs.PlaySound = true; _playSoundNextTick = false; } if (_emulator.Finished && !_keepOpenWhenDoneEmulating) { eventArgs.ShouldQuit = true; } if (_logPerformance) { _guiTickCounter++; if (_guiPerformanceWatch.ElapsedMilliseconds >= 1000) { Console.WriteLine("GUI ticks per second: " + _guiTickCounter); _guiTickCounter = 0; _guiPerformanceWatch.Restart(); } } if (_debug && eventArgs.KeyDown != null) { if (eventArgs.KeyDown == SDL2.SDL.SDL_Keycode.SDLK_SPACE) { _shouldBreak = true; } else if (eventArgs.KeyDown == SDL2.SDL.SDL_Keycode.SDLK_F5) { _shouldStep = false; _shouldBreak = false; } else if (eventArgs.KeyDown == SDL2.SDL.SDL_Keycode.SDLK_F10) { _shouldStep = true; _shouldBreak = false; } } }
public void StartLoop() { // Used to keep track of the time elapsed in each loop iteration. This is used to // notify the OnTick handlers so they can update their simulation, as well as throttle // the update loop to 60hz if needed. var stopwatch = new Stopwatch(); // Structure used to pass data to and from the OnTick handlers. We initialize it once // outside of the loop to avoid eating a ton of memory putting GC into a tailspin. var tickEventArgs = new GUITickEventArgs(); tickEventArgs.Keys = GetEmptyKeyDictionary(); // The SDL event polled for in each iteration of the loop. SDL.SDL_Event sdlEvent; while (true) { stopwatch.Restart(); tickEventArgs.KeyDown = null; while (SDL.SDL_PollEvent(out sdlEvent) != 0) { switch (sdlEvent.type) { // e.g. Command + Q case SDL.SDL_EventType.SDL_QUIT: // Break out of the SDL event loop, which will close the program. return; case SDL.SDL_EventType.SDL_KEYDOWN: tickEventArgs.KeyDown = sdlEvent.key.keysym.sym; UpdateKeys(tickEventArgs.Keys, sdlEvent.key.keysym.sym, true); break; case SDL.SDL_EventType.SDL_KEYUP: UpdateKeys(tickEventArgs.Keys, sdlEvent.key.keysym.sym, false); break; } } // Update the event arguments that will be sent with the event handler. tickEventArgs.ShouldRender = false; tickEventArgs.PlaySound = false; // Delegate out to the event handler so work can be done. if (OnTick != null) { OnTick(tickEventArgs); } // We only want to re-render if the frame buffer has changed since last time because // the SDL_RenderPresent method is relatively expensive and massively slows down the // amount of opcodes (ticks) we can execute. if (tickEventArgs.ShouldRender) { // Clear the screen. SDL.SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 0); SDL.SDL_RenderClear(_renderer); // Render screen from the updated the frame buffer. SDL.SDL_SetRenderDrawColor(_renderer, 255, 255, 255, 255); var frameBuffer = tickEventArgs.FrameBuffer; // TODO: Build out a 2D array of points so we can do a single call to SDL_RenderDrawPoints here. if (frameBuffer != null) { for (var x = 0; x < frameBuffer.GetLength(0); x++) { for (var y = 0; y < frameBuffer.GetLength(1); y++) { if (frameBuffer[x, y] == 1) { SDL.SDL_RenderDrawPoint(_renderer, x, y); } } } } SDL.SDL_RenderPresent(_renderer); } // If the event handler indicated a should needs to be played, do it now. if (tickEventArgs.PlaySound) { // TODO: Beep } // See if we need to delay to keep locked to ~ TARGET_FPS FPS. if (stopwatch.Elapsed.TotalMilliseconds < (1000 / _targetFPS)) { var delay = (1000 / _targetFPS) - stopwatch.Elapsed.TotalMilliseconds; SDL.SDL_Delay((uint)delay); } // If the event handler indicated we should quit, then stop. if (tickEventArgs.ShouldQuit) { return; } } }