Browse Source

Refactor emulator control. Add callback for SDL.

master
Ben Kurtovic 8 years ago
parent
commit
3b12d5cd12
3 changed files with 132 additions and 73 deletions
  1. +24
    -33
      src/emulator.c
  2. +96
    -36
      src/gamegear.c
  3. +12
    -4
      src/gamegear.h

+ 24
- 33
src/emulator.c View File

@@ -2,24 +2,29 @@
Released under the terms of the MIT License. See LICENSE for details. */

#include <signal.h>
#include <stdbool.h>
#include <unistd.h>

#include "emulator.h"
#include "logging.h"
#include "util.h"

#define NS_PER_FRAME (1000 * 1000 * 1000 / 60)

static volatile bool caught_signal;
static GameGear *global_gg;

/*
Signal handler for SIGINT.
*/
static void handle_sigint(int sig)
{
(void) sig; // We don't care
caught_signal = true;
(void) sig;
if (global_gg)
gamegear_power_off(global_gg);
}

/*
GameGear callback: handle SDL logic at the end of a frame.
*/
static void draw_frame(GameGear *gg)
{
(void) gg;
// TODO: SDL draw / switch buffers here
}

/*
@@ -29,34 +34,20 @@ static void handle_sigint(int sig)
*/
void emulate(GameGear *gg)
{
caught_signal = false;
global_gg = gg;
signal(SIGINT, handle_sigint);
gamegear_set_callback(gg, draw_frame);

DEBUG("Emulator powering GameGear")
gamegear_power(gg, true);

while (!caught_signal) {
uint64_t start = get_time_ns(), delta;
if (gamegear_simulate_frame(gg)) {
ERROR("caught exception: %s", gamegear_get_exception(gg))
if (DEBUG_LEVEL)
z80_dump_registers(&gg->cpu);
break;
}
// TODO: SDL draw / switch buffers here
delta = get_time_ns() - start;
if (delta < NS_PER_FRAME)
usleep((NS_PER_FRAME - delta) / 1000);
}

if (caught_signal) {
WARN("caught signal, stopping...")
if (DEBUG_LEVEL)
z80_dump_registers(&gg->cpu);
}
gamegear_simulate(gg);

DEBUG("Emulator unpowering GameGear")
gamegear_power(gg, false);
if (gamegear_get_exception(gg))
ERROR("caught exception: %s", gamegear_get_exception(gg))
else
WARN("caught signal, stopping...")
if (DEBUG_LEVEL)
gamegear_print_state(gg);

gamegear_clear_callback(gg);
signal(SIGINT, SIG_DFL);
global_gg = NULL;
}

+ 96
- 36
src/gamegear.c View File

@@ -2,6 +2,7 @@
Released under the terms of the MIT License. See LICENSE for details. */

#include <stdlib.h>
#include <unistd.h>

#include "gamegear.h"
#include "logging.h"
@@ -9,8 +10,11 @@

/* Clock speed in Hz was taken from the official Sega GG documentation */
#define CPU_CLOCK_SPEED (3579545.)
#define CYCLES_PER_FRAME (CPU_CLOCK_SPEED / 60)
#define CYCLES_PER_FRAME (CPU_CLOCK_SPEED / GG_FPS)
#define CYCLES_PER_LINE (CYCLES_PER_FRAME / VDP_LINES_PER_FRAME)
#define NS_PER_FRAME (1000 * 1000 * 1000 / GG_FPS)

#define SET_EXC(...) snprintf(gg->exc_buffer, GG_EXC_BUFF_SIZE, __VA_ARGS__);

/*
Create and return a pointer to a new GameGear object.
@@ -24,6 +28,7 @@ GameGear* gamegear_create()
z80_init(&gg->cpu, &gg->mmu, &gg->io);

gg->powered = false;
gg->callback = NULL;
gg->exc_buffer[0] = '\0';
return gg;
}
@@ -56,47 +61,59 @@ void gamegear_load(GameGear *gg, const ROM *rom)
}

/*
Set the GameGear object's power state (true = on; false = off).
Power on the GameGear.

This clears the exception buffer and executes boot code (e.g. clearing
memory and setting initial register values).
*/
static void power_on(GameGear *gg)
{
gg->exc_buffer[0] = '\0';
gg->powered = true;

Powering on the GameGear executes boot code (e.g. clearing memory and
setting initial register values) and starts the clock. Powering it off
stops the clock and clears any exception data.
mmu_power(&gg->mmu);
vdp_power(&gg->vdp);
io_power(&gg->io);
z80_power(&gg->cpu);
}

/*
Power off the GameGear.

Setting the power state to its current value has no effect.
This function *may* be used while the GameGear is running to trigger a safe
shutdown at the next opportunity. It is also reentrant. If the GameGear is
already off, it will do nothing.
*/
void gamegear_power(GameGear *gg, bool state)
void gamegear_power_off(GameGear *gg)
{
if (gg->powered == state)
return;
gg->powered = false;
}

if (state) {
mmu_power(&gg->mmu);
vdp_power(&gg->vdp);
io_power(&gg->io);
z80_power(&gg->cpu);
} else {
gg->exc_buffer[0] = '\0';
}
gg->powered = state;
/*
Set a callback to be triggered whenever the GameGear completes a frame.
*/
void gamegear_set_callback(GameGear *gg, GGFrameCallback callback)
{
gg->callback = callback;
}

/*
Reset the GameGear's frame callback function.
*/
void gamegear_clear_callback(GameGear *gg)
{
gg->callback = NULL;
}

/*
Simulate the GameGear for one frame.

This function simulates the number of clock cycles corresponding to 1/60th
of a second. If the system is powered off, this function does nothing.

The return value indicates whether an exception flag has been set
somewhere. If true, emulation must be stopped. gamegear_get_exception() can
be used to fetch exception information. Power-cycling the GameGear with
gamegear_power(gg, false) followed by gamegear_power(gg, true) will reset
the exception flag and allow emulation to restart normally.
of a second. The return value indicates whether an exception flag has been
set somewhere. If true, emulation must be stopped.
*/
bool gamegear_simulate_frame(GameGear *gg)
static bool simulate_frame(GameGear *gg)
{
if (!gg->powered)
return false;

size_t line;
bool except;

@@ -110,20 +127,55 @@ bool gamegear_simulate_frame(GameGear *gg)
}

/*
Simulate the GameGear.

The GameGear must start out in an unpowered state; it will be powered only
during the simulation. This function blocks until the simulation ends,
either by an exception occurring or someone setting the GameGear's
'powered' flag to false.

If a callback has been set with gamegear_set_callback(), then we'll trigger
it after every frame has been simulated (sixty times per second).

Exceptions can be retrieved after this call with gamegear_get_exception().
If the simulation ended normally, then that function will return NULL.
*/
void gamegear_simulate(GameGear *gg)
{
if (gg->powered)
return;

DEBUG("GameGear: powering on")
power_on(gg);

while (gg->powered) {
uint64_t start = get_time_ns(), delta;

if (simulate_frame(gg) || !gg->powered)
break;
if (gg->callback)
gg->callback(gg);

delta = get_time_ns() - start;
if (delta < NS_PER_FRAME)
usleep((NS_PER_FRAME - delta) / 1000);
}

DEBUG("GameGear: powering off")
gamegear_power_off(gg);
}

/*
If an exception flag has been set in the GameGear, return the reason.

This function returns a const pointer to a buffer holding a human-readable
exception string (although it may be cryptic to an end-user). The buffer is
owned by the GameGear object and should not be freed - its contents last
until the GameGear's power state is changed. If no exception flag is set,
this function returns NULL.
until the GameGear is powered on. If no exception flag is set, this
function returns NULL.
*/
const char* gamegear_get_exception(GameGear *gg)
{
#define SET_EXC(...) snprintf(gg->exc_buffer, GG_EXC_BUFF_SIZE, __VA_ARGS__);
if (!gg->powered)
return NULL;

if (!gg->exc_buffer[0]) {
if (gg->cpu.except) {
switch (gg->cpu.exc_code) {
@@ -145,5 +197,13 @@ const char* gamegear_get_exception(GameGear *gg)
}
}
return gg->exc_buffer;
#undef SET_EXC
}

/*
@DEBUG_LEVEL
Print out some state info to stdout: Z80 and VDP register values, etc.
*/
void gamegear_print_state(const GameGear *gg)
{
z80_dump_registers(&gg->cpu);
}

+ 12
- 4
src/gamegear.h View File

@@ -11,16 +11,21 @@
#include "rom.h"
#include "z80.h"

#define GG_FPS 60
#define GG_EXC_BUFF_SIZE 128

/* Structs */
/* Structs, etc. */

typedef struct {
struct GameGear;
typedef void (*GGFrameCallback)(struct GameGear*);

typedef struct GameGear {
Z80 cpu;
MMU mmu;
VDP vdp;
IO io;
bool powered;
GGFrameCallback callback;
char exc_buffer[GG_EXC_BUFF_SIZE];
} GameGear;

@@ -29,6 +34,9 @@ typedef struct {
GameGear* gamegear_create();
void gamegear_destroy(GameGear*);
void gamegear_load(GameGear*, const ROM*);
void gamegear_power(GameGear*, bool);
bool gamegear_simulate_frame(GameGear*);
void gamegear_simulate(GameGear*);
void gamegear_power_off(GameGear*);
void gamegear_set_callback(GameGear*, GGFrameCallback);
void gamegear_clear_callback(GameGear*);
const char* gamegear_get_exception(GameGear*);
void gamegear_print_state(const GameGear*);

Loading…
Cancel
Save