437 lines
10 KiB
C
437 lines
10 KiB
C
|
/*
|
||
|
* TilEm II
|
||
|
*
|
||
|
* Copyright (c) 2011 Benjamin Moody
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License as
|
||
|
* published by the Free Software Foundation, either version 3 of the
|
||
|
* License, or (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but
|
||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
# include <config.h>
|
||
|
#endif
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <errno.h>
|
||
|
#include <gtk/gtk.h>
|
||
|
#include <ticalcs.h>
|
||
|
#include <tilem.h>
|
||
|
#include <scancodes.h>
|
||
|
|
||
|
#include "gui.h"
|
||
|
#include "emucore.h"
|
||
|
|
||
|
#define MICROSEC_PER_TICK 10000
|
||
|
|
||
|
typedef struct {
|
||
|
TilemCalcEmulator *emu;
|
||
|
TilemTaskMainFunc mainf;
|
||
|
TilemTaskFinishedFunc finishedf;
|
||
|
gpointer userdata;
|
||
|
gboolean cancelled;
|
||
|
} Task;
|
||
|
|
||
|
/* Add a task to the queue. */
|
||
|
void tilem_calc_emulator_begin(TilemCalcEmulator *emu,
|
||
|
TilemTaskMainFunc mainf,
|
||
|
TilemTaskFinishedFunc finishedf,
|
||
|
gpointer data)
|
||
|
{
|
||
|
Task *task;
|
||
|
|
||
|
g_return_if_fail(emu != NULL);
|
||
|
g_return_if_fail(mainf != NULL);
|
||
|
|
||
|
task = g_slice_new0(Task);
|
||
|
task->emu = emu;
|
||
|
task->mainf = mainf;
|
||
|
task->finishedf = finishedf;
|
||
|
task->userdata = data;
|
||
|
|
||
|
tilem_calc_emulator_lock(emu);
|
||
|
g_queue_push_tail(emu->task_queue, task);
|
||
|
tilem_calc_emulator_unlock(emu);
|
||
|
}
|
||
|
|
||
|
/* Cancel all pending tasks. If a task is currently running, this
|
||
|
will wait for it to finish. */
|
||
|
void tilem_calc_emulator_cancel_tasks(TilemCalcEmulator *emu)
|
||
|
{
|
||
|
GQueue *oldqueue;
|
||
|
Task *task;
|
||
|
|
||
|
tilem_calc_emulator_lock(emu);
|
||
|
emu->task_abort = TRUE;
|
||
|
emu->link_update->cancel = TRUE;
|
||
|
oldqueue = emu->task_queue;
|
||
|
emu->task_queue = g_queue_new();
|
||
|
tilem_calc_emulator_unlock(emu);
|
||
|
|
||
|
while ((task = g_queue_pop_head(oldqueue))) {
|
||
|
if (task->finishedf)
|
||
|
(*task->finishedf)(emu, task->userdata, TRUE);
|
||
|
g_slice_free(Task, task);
|
||
|
}
|
||
|
|
||
|
g_queue_free(oldqueue);
|
||
|
|
||
|
g_mutex_lock(emu->calc_mutex);
|
||
|
while (emu->task_busy)
|
||
|
g_cond_wait(emu->task_finished_cond, emu->calc_mutex);
|
||
|
emu->task_abort = FALSE;
|
||
|
emu->link_update->cancel = FALSE;
|
||
|
g_cond_broadcast(emu->calc_wakeup_cond);
|
||
|
g_mutex_unlock(emu->calc_mutex);
|
||
|
}
|
||
|
|
||
|
/* Check if calculator is powered off */
|
||
|
static gboolean calc_asleep(TilemCalcEmulator *emu)
|
||
|
{
|
||
|
return (emu->calc->z80.halted
|
||
|
&& !emu->calc->z80.interrupts
|
||
|
&& !emu->calc->poweronhalt
|
||
|
&& !emu->key_queue_timer);
|
||
|
}
|
||
|
|
||
|
static gboolean refresh_lcd(gpointer data)
|
||
|
{
|
||
|
TilemCalcEmulator* emu = data;
|
||
|
|
||
|
if (emu->ewin)
|
||
|
tilem_emulator_window_refresh_lcd(emu->ewin);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/* Update screen for display while paused */
|
||
|
static void update_screen_mono(TilemCalcEmulator *emu)
|
||
|
{
|
||
|
g_mutex_lock(emu->lcd_mutex);
|
||
|
|
||
|
tilem_lcd_get_frame(emu->calc, emu->lcd_buffer);
|
||
|
|
||
|
if (!emu->lcd_update_pending) {
|
||
|
emu->lcd_update_pending = TRUE;
|
||
|
g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL);
|
||
|
}
|
||
|
|
||
|
g_mutex_unlock(emu->lcd_mutex);
|
||
|
}
|
||
|
|
||
|
/* idle callback to update progress bar */
|
||
|
static gboolean pbar_update(gpointer data)
|
||
|
{
|
||
|
TilemCalcEmulator *emu = data;
|
||
|
progress_bar_update(emu);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static void update_progress(TilemCalcEmulator *emu, gboolean force)
|
||
|
{
|
||
|
if (force || emu->progress_changed)
|
||
|
g_idle_add(&pbar_update, emu);
|
||
|
emu->progress_changed = FALSE;
|
||
|
}
|
||
|
|
||
|
static gboolean show_debugger(gpointer data)
|
||
|
{
|
||
|
TilemCalcEmulator* emu = data;
|
||
|
|
||
|
if (emu->dbg)
|
||
|
tilem_debugger_show(emu->dbg);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
#define BREAK_MASK (TILEM_STOP_BREAKPOINT \
|
||
|
| TILEM_STOP_INVALID_INST \
|
||
|
| TILEM_STOP_UNDOCUMENTED_INST)
|
||
|
|
||
|
/* Run one iteration of the emulator. */
|
||
|
dword tilem_em_run(TilemCalcEmulator *emu, int linkmode,
|
||
|
dword events, dword ff_events, gboolean keep_awake,
|
||
|
int timeout, int *elapsed)
|
||
|
{
|
||
|
dword all_events, ev_auto, ev_user;
|
||
|
int rem;
|
||
|
gulong tcur, delaytime;
|
||
|
|
||
|
if (emu->exiting || emu->task_abort) {
|
||
|
if (elapsed) *elapsed = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
else if (emu->paused) {
|
||
|
update_screen_mono(emu);
|
||
|
update_progress(emu, TRUE);
|
||
|
g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
|
||
|
update_progress(emu, TRUE);
|
||
|
g_timer_elapsed(emu->timer, &emu->timevalue);
|
||
|
if (elapsed) *elapsed = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
else if (!keep_awake && calc_asleep(emu)) {
|
||
|
update_progress(emu, FALSE);
|
||
|
update_screen_mono(emu);
|
||
|
g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
|
||
|
g_timer_elapsed(emu->timer, &emu->timevalue);
|
||
|
if (elapsed) *elapsed = timeout;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
update_progress(emu, FALSE);
|
||
|
|
||
|
all_events = events | BREAK_MASK;
|
||
|
|
||
|
emu->calc->linkport.linkemu = linkmode;
|
||
|
emu->calc->z80.stop_mask = ~all_events;
|
||
|
|
||
|
tilem_z80_run_time(emu->calc, timeout, &rem);
|
||
|
|
||
|
ev_user = emu->calc->z80.stop_reason & events;
|
||
|
ev_auto = emu->calc->z80.stop_reason & ~events;
|
||
|
|
||
|
if (elapsed) *elapsed = timeout - rem;
|
||
|
|
||
|
if (ev_auto & BREAK_MASK) {
|
||
|
emu->paused = TRUE;
|
||
|
g_idle_add(&show_debugger, emu);
|
||
|
}
|
||
|
|
||
|
if (emu->limit_speed
|
||
|
&& !(ff_events & ev_user)
|
||
|
&& ff_events != TILEM_EM_ALWAYS_FF) {
|
||
|
emu->timevalue += timeout - rem;
|
||
|
g_timer_elapsed(emu->timer, &tcur);
|
||
|
|
||
|
/* emu->timevalue is the "ideal" time when the
|
||
|
operation should be completed. If emu->timevalue
|
||
|
is greater than tcur, we're running faster than
|
||
|
real time. Try to sleep for (emu->timevalue -
|
||
|
tcur) microseconds.
|
||
|
|
||
|
If emu->timevalue is less than tcur, we're running
|
||
|
slower than real time. If the difference is small,
|
||
|
just keep going and hope we'll catch up later.
|
||
|
|
||
|
If the difference is substantial (more than 1/10
|
||
|
second in either direction), re-synchronize. */
|
||
|
|
||
|
delaytime = emu->timevalue - tcur;
|
||
|
|
||
|
if (delaytime <= (gulong) 100000 + timeout) {
|
||
|
tilem_em_unlock(emu);
|
||
|
g_usleep(delaytime);
|
||
|
tilem_em_lock(emu);
|
||
|
}
|
||
|
else {
|
||
|
tilem_em_check_yield(emu);
|
||
|
if (delaytime < (gulong) -100000)
|
||
|
emu->timevalue = tcur;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
tilem_em_check_yield(emu);
|
||
|
}
|
||
|
|
||
|
return ev_user;
|
||
|
}
|
||
|
|
||
|
static gboolean taskfinished(gpointer data)
|
||
|
{
|
||
|
Task *task = data;
|
||
|
|
||
|
if (task->finishedf)
|
||
|
(*task->finishedf)(task->emu, task->userdata, task->cancelled);
|
||
|
|
||
|
g_slice_free(Task, task);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static void run_task(TilemCalcEmulator *emu, Task *task)
|
||
|
{
|
||
|
gboolean status;
|
||
|
|
||
|
emu->task_busy = TRUE;
|
||
|
status = (*task->mainf)(emu, task->userdata);
|
||
|
|
||
|
g_idle_add(&taskfinished, task);
|
||
|
|
||
|
if (!status) {
|
||
|
while ((task = g_queue_pop_head(emu->task_queue))) {
|
||
|
task->cancelled = TRUE;
|
||
|
g_idle_add(&taskfinished, task);
|
||
|
}
|
||
|
}
|
||
|
emu->task_busy = FALSE;
|
||
|
}
|
||
|
|
||
|
/* Main loop */
|
||
|
gpointer tilem_em_main(gpointer data)
|
||
|
{
|
||
|
TilemCalcEmulator *emu = data;
|
||
|
Task *task;
|
||
|
|
||
|
tilem_em_lock(emu);
|
||
|
|
||
|
g_timer_start(emu->timer);
|
||
|
g_timer_elapsed(emu->timer, &emu->timevalue);
|
||
|
|
||
|
while (!emu->exiting) {
|
||
|
task = g_queue_pop_head(emu->task_queue);
|
||
|
if (task) {
|
||
|
run_task(emu, task);
|
||
|
}
|
||
|
else if (emu->task_abort) {
|
||
|
g_cond_broadcast(emu->task_finished_cond);
|
||
|
g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
|
||
|
}
|
||
|
else {
|
||
|
tilem_em_run(emu, 0, 0, 0, FALSE,
|
||
|
MICROSEC_PER_TICK, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tilem_em_unlock(emu);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Run the calculator for a short time. */
|
||
|
void tilem_em_delay(TilemCalcEmulator *emu, int timeout, gboolean ff)
|
||
|
{
|
||
|
int t;
|
||
|
G_GNUC_UNUSED dword events;
|
||
|
|
||
|
while (!emu->task_abort && timeout > 0) {
|
||
|
t = MIN(MICROSEC_PER_TICK, timeout);
|
||
|
events = tilem_em_run(emu, 0, 0,
|
||
|
(ff ? TILEM_EM_ALWAYS_FF : 0), TRUE,
|
||
|
t, &t);
|
||
|
timeout -= t;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define LINK_EVENTS (TILEM_STOP_LINK_READ_BYTE \
|
||
|
| TILEM_STOP_LINK_WRITE_BYTE \
|
||
|
| TILEM_STOP_LINK_ERROR)
|
||
|
|
||
|
static int run_until_ready(TilemCalcEmulator *emu, int timeout, gboolean ff)
|
||
|
{
|
||
|
int t;
|
||
|
dword events;
|
||
|
|
||
|
emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_GRAY;
|
||
|
while (!emu->task_abort && timeout > 0) {
|
||
|
if (tilem_linkport_graylink_ready(emu->calc))
|
||
|
return 0;
|
||
|
|
||
|
t = MIN(MICROSEC_PER_TICK, timeout);
|
||
|
events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
|
||
|
LINK_EVENTS, (ff ? LINK_EVENTS : 0), TRUE,
|
||
|
t, &t);
|
||
|
|
||
|
timeout -= t;
|
||
|
if (events & TILEM_STOP_LINK_ERROR)
|
||
|
break;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Send a byte to the calculator. */
|
||
|
int tilem_em_send_byte(TilemCalcEmulator *emu, unsigned value,
|
||
|
int timeout, gboolean ff)
|
||
|
{
|
||
|
if (run_until_ready(emu, timeout, ff))
|
||
|
return -1;
|
||
|
if (tilem_linkport_graylink_send_byte(emu->calc, value))
|
||
|
return -1;
|
||
|
if (run_until_ready(emu, timeout, ff))
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Receive a byte from the calculator. */
|
||
|
int tilem_em_get_byte(TilemCalcEmulator *emu, int timeout, gboolean ff)
|
||
|
{
|
||
|
int t, v;
|
||
|
dword events;
|
||
|
|
||
|
while (!emu->task_abort && timeout > 0) {
|
||
|
v = tilem_linkport_graylink_get_byte(emu->calc);
|
||
|
if (v >= 0)
|
||
|
return v;
|
||
|
|
||
|
t = MIN(MICROSEC_PER_TICK, timeout);
|
||
|
events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
|
||
|
LINK_EVENTS, (ff ? LINK_EVENTS : 0), FALSE,
|
||
|
t, &t);
|
||
|
timeout -= t;
|
||
|
if (events & TILEM_STOP_LINK_ERROR)
|
||
|
break;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Wake up calculator if currently turned off. */
|
||
|
void tilem_em_wake_up(TilemCalcEmulator *emu, gboolean ff)
|
||
|
{
|
||
|
tilem_em_delay(emu, 1000000, ff);
|
||
|
|
||
|
if (!calc_asleep(emu))
|
||
|
return;
|
||
|
|
||
|
tilem_keypad_press_key(emu->calc, TILEM_KEY_ON);
|
||
|
tilem_em_delay(emu, 500000, ff);
|
||
|
tilem_keypad_release_key(emu->calc, TILEM_KEY_ON);
|
||
|
tilem_em_delay(emu, 500000, ff);
|
||
|
}
|
||
|
|
||
|
/* Set progress window title. Set TITLE to NULL to disable progress
|
||
|
window. */
|
||
|
void tilem_em_set_progress_title(TilemCalcEmulator *emu, const char *title)
|
||
|
{
|
||
|
g_mutex_lock(emu->pbar_mutex);
|
||
|
g_free(emu->pbar_title);
|
||
|
g_free(emu->pbar_status);
|
||
|
emu->pbar_title = title ? g_strdup(title) : NULL;
|
||
|
emu->pbar_status = NULL;
|
||
|
emu->pbar_progress = 0.0;
|
||
|
if (!emu->pbar_update_pending)
|
||
|
emu->progress_changed = TRUE;
|
||
|
emu->pbar_update_pending = TRUE;
|
||
|
g_mutex_unlock(emu->pbar_mutex);
|
||
|
}
|
||
|
|
||
|
/* Set current progress information. FRAC is the estimated fraction
|
||
|
of the task completed; STATUS is a text description of the current
|
||
|
operation. */
|
||
|
void tilem_em_set_progress(TilemCalcEmulator *emu, gdouble frac,
|
||
|
const char *status)
|
||
|
{
|
||
|
g_mutex_lock(emu->pbar_mutex);
|
||
|
|
||
|
if (!emu->pbar_status || !status
|
||
|
|| strcmp(status, emu->pbar_status)) {
|
||
|
g_free(emu->pbar_status);
|
||
|
emu->pbar_status = status ? g_strdup(status) : NULL;
|
||
|
}
|
||
|
|
||
|
emu->pbar_progress = frac;
|
||
|
|
||
|
if (!emu->pbar_update_pending)
|
||
|
emu->progress_changed = TRUE;
|
||
|
emu->pbar_update_pending = TRUE;
|
||
|
|
||
|
g_mutex_unlock(emu->pbar_mutex);
|
||
|
}
|