linappleii / linapple

Apple 2e emulator
GNU General Public License v2.0
134 stars 61 forks source link

Explore creating a clean game loop for linapple #12

Open knghtbrd opened 6 years ago

knghtbrd commented 6 years ago

This is kind of a structural change, and it might reflect quite a divergence from AppleWin, but I think we ought to do it. First, a survey of how SDL is used in LinApple by source file:

File SDL parts used
Applewin.cpp Event processing, Init/Quit, Keyboard
DiskChoose.cpp Buffer flipping, Event processing, Keyboard, Screen drawing, Surface management
Disk.cpp Window management
DiskFTP.cpp Buffer flipping, Event processing, Keyboard, Screen drawing, Surface management
Frame.cpp Buffer flipping, Event processing, File writing, Init/Quit, Keyboard, Mouse cursor, Screen drawing, Surface management, Window creation, Window management
Joystick.cpp Keyboard, Joystick
Keyboard.cpp Keyboard
SoundCore.cpp Audio, / Init/Quit /
Speaker.cpp Audio
stretch.cpp Surface management
Video.cpp Screen drawing, Surface management

Okay, so video doesn't handle window creation or management at all and is purely for displaying the Apple II screen. There's some tight delay-until-event-we-want (and ignore all others like CLOSE for example) loops in around DiskChoose/DiskFTP. Frame seems to do a lot of everything, and you'd expect that but you'd also expect it to be delegating tasks to other subsystems.

In game development, what's typically done for single-threaded games is a loop: draw, input, think, flip. The draw is done first because if the 3D hardware hasn't finished in other threads or on the card, your attempt to flip buffers will block until it's done. A lot of games are still written this way despite the prevalence of multi-core CPUs because they're easier to debug.

I'd like to transition to this sort of frame loop in LinApple. Not having it is kinda why I've backed off of working on #5. What I'd like to do is to create stub init/shutdown functions respectively above and below all other parts of main(). As initialization code is identified, that code should be moved to be called from init. Any corresponding shutdown code should be called from that function in reverse order. If initialization code cannot be moved to init for some reason, it should be marked along with what the problem is so that when the problem is addressed, it can be moved.

I think I'd wait to move on to the frame loop until that's done to the greatest extent possible. From there I'd stub in draw, input, and think before the current per-frame code, which should be classified as "think" logic. At that point, I would actually start with the code that is least behaved such as the modal DiskChoose/DiskFTP and implement conditional draw and input code for those modalities (handing essential system events this time…)

Once everything's drawing and inputting and thinking in that order, refactoring shouldn't be too hard to clean it all up in short order.

Famous last words…

rhaleblian commented 5 years ago

When looking at the code for the first time similar points came up in my mind.

Some of these changes would work towards the nefarious idea of making linapple into a libretro core.