/* event.c: Routines needed for dealing with the event list Copyright (c) 2000-2004 Philip Kendall $Id: event.c,v 1.57 2007/02/02 16:21:51 pak21 Exp $ 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include #include #include #include "debugger/debugger.h" #include "display.h" #include "event.h" #include "fuse.h" #include "machine.h" #include "psg.h" #include "rzx.h" #include "tape.h" #include "timer.h" #include "trdos.h" #include "ui/ui.h" #include "ui/uijoystick.h" #include "spectrum.h" #include "timer.h" #include "z80/z80.h" /* A large value to mean `no events due' */ const libspectrum_dword event_no_events = 0xffffffff; /* When will the next event happen? */ libspectrum_dword event_next_event; /* The actual list of events */ static GSList* event_list; /* An event ready to be reused */ static event_t *event_free; /* Comparision function so events stay in t-state order */ static gint event_add_cmp( gconstpointer a, gconstpointer b ); /* User function for event_interrupt(...) */ void event_reduce_tstates(gpointer data,gpointer user_data); /* Make the event have no effect if it matches the given type */ static void set_event_null( gpointer data, gpointer user_data ); /* Free the memory used by a specific entry */ void event_free_entry(gpointer data, gpointer user_data); /* Force events between now and the next interrupt to happen */ static int event_force_events( void ); /* Set up the event list */ int event_init(void) { event_list=NULL; event_free=NULL; event_next_event=event_no_events; return 0; } /* Add an event at the correct place in the event list */ int event_add( libspectrum_dword event_time, event_type type ) { event_t *ptr; if( event_free ) { ptr=event_free; event_free=NULL; } else { ptr=(event_t*)malloc(sizeof(event_t)); if(!ptr) return 1; } ptr->tstates= event_time; ptr->type=type; if( event_time < event_next_event ) { event_next_event = event_time; event_list=g_slist_prepend(event_list,(gpointer)ptr); } else { event_list=g_slist_insert_sorted(event_list,(gpointer)ptr,event_add_cmp); } return 0; } /* Comparision function so events stay in t-state and event type order */ static gint event_add_cmp( gconstpointer a1, gconstpointer b1 ) { const event_t *a = a1, *b = b1; return a->tstates != b->tstates ? a->tstates - b->tstates : a->type - b->type; } /* Do all events which have passed */ int event_do_events(void) { event_t *ptr; while(event_next_event <= tstates) { ptr= ( (event_t*) (event_list->data) ); /* Remove the event from the list *before* processing */ event_list=g_slist_remove(event_list,ptr); if( event_list == NULL ) { event_next_event = event_no_events; } else { event_next_event= ( (event_t*) (event_list->data) ) -> tstates; } switch(ptr->type) { case EVENT_TYPE_EDGE: tape_next_edge( ptr->tstates ); break; case EVENT_TYPE_TIMER: timer_frame( ptr->tstates ); break; case EVENT_TYPE_FRAME: if( rzx_playback ) event_force_events(); rzx_frame(); psg_frame(); spectrum_frame(); z80_interrupt(); ui_joystick_poll(); timer_estimate_speed(); debugger_add_time_events(); ui_event(); ui_error_frame(); break; case EVENT_TYPE_INTERRUPT: /* Retriggered interrupt; firstly, ignore if we're doing RZX playback as all interrupts are generated by the RZX code */ if( rzx_playback ) break; /* Otherwise, see if we actually accept an interrupt. If we do and we're doing RZX recording, store a frame */ if( z80_interrupt() ) rzx_frame(); break; case EVENT_TYPE_NMI: z80_nmi(); break; case EVENT_TYPE_NULL: /* Do nothing */ break; case EVENT_TYPE_TRDOS_CMD_DONE: trdos_event_cmd_done( ptr->tstates ); break; case EVENT_TYPE_TRDOS_INDEX: trdos_event_index( ptr->tstates ); break; case EVENT_TYPE_BREAKPOINT: debugger_check( DEBUGGER_BREAKPOINT_TYPE_TIME, 0 ); break; default: ui_error( UI_ERROR_ERROR, "unknown event type %d", ptr->type ); break; } if( event_free ) { free( ptr ); } else { event_free=ptr; } } return 0; } /* Called at end of frame to reduce T-state count of all entries */ int event_frame( libspectrum_dword tstates_per_frame ) { g_slist_foreach(event_list, event_reduce_tstates, &tstates_per_frame ); if( event_list == NULL ) { event_next_event = event_no_events; } else { event_next_event= ( (event_t*) (event_list->data) ) -> tstates; } return 0; } /* User function for event_interrupt(...) */ void event_reduce_tstates(gpointer data,gpointer user_data) { event_t *ptr=(event_t*)data; libspectrum_dword *tstates_per_frame = (libspectrum_dword*)user_data; ptr->tstates -= (*tstates_per_frame) ; } /* Remove all events of a specific type from the stack */ int event_remove_type( event_type type ) { /* FIXME: this is an ugly hack. Just set all events of the given type to be of a null type, meaning they do nothing */ g_slist_foreach( event_list, set_event_null, &type ); return 0; } static void set_event_null( gpointer data, gpointer user_data ) { event_t *ptr = (event_t*)data; event_type type = *(event_type*)user_data; if( ptr->type == type ) ptr->type = EVENT_TYPE_NULL; } /* Clear the event stack */ int event_reset(void) { g_slist_foreach(event_list,event_free_entry,NULL); g_slist_free(event_list); event_list=NULL; event_next_event=event_no_events; if( event_free ) free( event_free ); event_free=NULL; return 0; } /* Free the memory used by a specific entry */ void event_free_entry( gpointer data, gpointer user_data GCC_UNUSED ) { event_t *ptr=(event_t*)data; free(ptr); } /* Do all events that would happen between the current time and when the next interrupt will occur; called only when RZX playback is in effect */ static int event_force_events( void ) { while( event_next_event < machine_current->timings.tstates_per_frame ) { /* Jump along to the next event */ tstates = event_next_event; /* And do that event */ event_do_events(); } return 0; } /* Call a user-supplied function for every event in the current list */ int event_foreach( void (*function)( gpointer data, gpointer user_data), gpointer user_data ) { g_slist_foreach( event_list, function, user_data ); return 0; } /* A textual representation of each event type */ const char * event_name( event_type type ) { switch( type ) { case EVENT_TYPE_EDGE: return "Tape edge"; case EVENT_TYPE_FRAME: return "End of frame"; case EVENT_TYPE_INTERRUPT: return "Retriggered interrupt"; case EVENT_TYPE_NMI: return "Non-maskable interrupt"; case EVENT_TYPE_NULL: return "[Deleted event]"; case EVENT_TYPE_TRDOS_CMD_DONE: return "End of TR-DOS command"; case EVENT_TYPE_TRDOS_INDEX: return "TR-DOS index"; case EVENT_TYPE_BREAKPOINT: return "Breakpoint"; case EVENT_TYPE_TIMER: return "Timer"; } return "[Unknown event type]"; } /* Tidy-up function called at end of emulation */ int event_end(void) { return event_reset(); }