#include "std.h" #include "call_out.h" #include "backend.h" #include "comm.h" #include "port.h" #include "eoperators.h" #include "sprintf.h" #include "eval.h" #define DBG(x) debug(call_out, x) /* * This file implements delayed calls of functions. * Static functions can not be called this way. * * Allocate the structures several in one chunk, to get rid of malloc * overhead. */ #define CHUNK_SIZE 20 typedef struct pending_call_s { int delta; union string_or_func function; object_t *ob; array_t *vs; struct pending_call_s *next; #ifdef THIS_PLAYER_IN_CALL_OUT object_t *command_giver; #endif #ifdef CALLOUT_HANDLES int handle; #endif } pending_call_t; static pending_call_t *call_list[CALLOUT_CYCLE_SIZE]; static pending_call_t *call_list_free; static int num_call; #ifdef CALLOUT_HANDLES static int unique = 0; #endif static void free_call (pending_call_t *); static void free_called_call (pending_call_t *); void remove_all_call_out (object_t *); /* * Free a call out structure. */ static void free_called_call (pending_call_t * cop) { cop->next = call_list_free; if (cop->ob) { free_string(cop->function.s); free_object(&cop->ob, "free_call"); } else { free_funp(cop->function.f); } cop->function.s = 0; #ifdef THIS_PLAYER_IN_CALL_OUT if (cop->command_giver){ free_object(&cop->command_giver, "free_call"); cop->command_giver = 0; } #endif cop->ob = 0; call_list_free = cop; } INLINE_STATIC void free_call (pending_call_t * cop) { if (cop->vs) free_array(cop->vs); free_called_call(cop); } /* * Setup a new call out. */ #ifdef CALLOUT_HANDLES int #else void #endif new_call_out (object_t * ob, svalue_t * fun, int delay, int num_args, svalue_t * arg) { pending_call_t *cop, **copp; int tm; if (delay < 0) delay = 0; DBG(("new_call_out: /%s delay %i", ob->obname, delay)); if (!call_list_free) { int i; call_list_free = CALLOCATE(CHUNK_SIZE, pending_call_t, TAG_CALL_OUT, "new_call_out: call_list_free"); for (i = 0; i < CHUNK_SIZE - 1; i++) call_list_free[i].next = &call_list_free[i + 1]; call_list_free[CHUNK_SIZE - 1].next = 0; num_call += CHUNK_SIZE; } cop = call_list_free; call_list_free = call_list_free->next; if (fun->type == T_STRING) { DBG((" function: %s", fun->u.string)); cop->function.s = make_shared_string(fun->u.string); cop->ob = ob; add_ref(ob, "call_out"); } else { DBG((" function: ")); cop->function.f = fun->u.fp; fun->u.fp->hdr.ref++; cop->ob = 0; } #ifdef THIS_PLAYER_IN_CALL_OUT cop->command_giver = command_giver; /* save current user context */ if (command_giver) add_ref(command_giver, "new_call_out"); /* Bump its ref */ #endif if (num_args > 0) { cop->vs = allocate_empty_array(num_args); memcpy(cop->vs->item, arg, sizeof(svalue_t) * num_args); } else cop->vs = 0; /* Find out which slot this one fits in */ tm = (delay + current_time) & (CALLOUT_CYCLE_SIZE - 1); /* number of cycles */ delay = delay / CALLOUT_CYCLE_SIZE; DBG(("Current time: %i Executes at: %i Slot: %i Delay: %i", current_time, current_time + delay, tm, delay)); for (copp = &call_list[tm]; *copp; copp = &(*copp)->next) { if ((*copp)->delta > delay) { (*copp)->delta -= delay; cop->delta = delay; cop->next = *copp; *copp = cop; #ifdef CALLOUT_HANDLES tm += CALLOUT_CYCLE_SIZE * ++unique; cop->handle = tm; return tm; #else return; #endif } delay -= (*copp)->delta; } *copp = cop; cop->delta = delay; cop->next = 0; #ifdef CALLOUT_HANDLES tm += CALLOUT_CYCLE_SIZE * ++unique; cop->handle = tm; return tm; #endif } /* * See if there are any call outs to be called. Set the 'command_giver' * if it is a living object. Check for shadowing objects, which may also * be living objects. */ void call_out() { int extra, real_time; static pending_call_t *cop = 0; error_context_t econ; VOLATILE int tm; current_interactive = 0; /* could be still allocated if an error occured during a call_out */ if (cop) { free_called_call(cop); cop = 0; } real_time = get_current_time(); DBG(("Calling call_outs: current_time: %i real_time: %i difference: %i", current_time, real_time, real_time - current_time)); /* Slowly advance the clock forward towards real_time, doing call_outs * as we go. */ save_context(&econ); while (1) { tm = current_time & (CALLOUT_CYCLE_SIZE - 1); DBG((" slot %i", tm)); while (call_list[tm] && call_list[tm]->delta == 0) { object_t *ob, *new_command_giver; /* * Move the first call_out out of the chain. */ cop = call_list[tm]; call_list[tm] = call_list[tm]->next; ob = (cop->ob ? cop->ob : cop->function.f->hdr.owner); DBG((" /%s", (ob ? ob->obname : "(null)"))); if (!ob || (ob->flags & O_DESTRUCTED)) { DBG((" (destructed)")); free_call(cop); cop = 0; } else { if (SETJMP(econ.context)) { restore_context(&econ); if (max_eval_error) { debug_message("Maximum evaluation cost reached while trying to process call_outs\n"); pop_context(&econ); return; } } else { object_t *ob; ob = cop->ob; #ifndef NO_SHADOWS if (ob) while (ob->shadowing) ob = ob->shadowing; #endif new_command_giver = 0; #ifdef THIS_PLAYER_IN_CALL_OUT if (cop->command_giver && !(cop->command_giver->flags & O_DESTRUCTED)) { new_command_giver = cop->command_giver; } else if (ob && (ob->flags & O_LISTENER)) { new_command_giver = ob; } if (new_command_giver) DBG((" command_giver: /%s", new_command_giver->obname)); #endif save_command_giver(new_command_giver); /* current object no longer set */ if (cop->vs) { array_t *vec = cop->vs; svalue_t *svp = vec->item + vec->size; while (svp-- > vec->item) { if (svp->type == T_OBJECT && (svp->u.ob->flags & O_DESTRUCTED)) { free_object(&svp->u.ob, "call_out"); *svp = const0u; } } /* cop->vs is ref one */ extra = cop->vs->size; transfer_push_some_svalues(cop->vs->item, extra); free_empty_array(cop->vs); } else extra = 0; //reset_eval_cost(); set_eval(max_cost); if (cop->ob) { if (cop->function.s[0] == APPLY___INIT_SPECIAL_CHAR) error("Illegal function name\n"); (void) apply(cop->function.s, cop->ob, extra, ORIGIN_INTERNAL); } else { (void) call_function_pointer(cop->function.f, extra); } restore_command_giver(); } free_called_call(cop); cop = 0; } } /* Ok, no more scheduled call_outs for current_time */ if (current_time < real_time) { /* Time marches onward! */ if (call_list[tm]) call_list[tm]->delta--; current_time++; DBG((" current_time = %i", current_time)); if(!(current_time%HEARTBEAT_INTERVAL)) call_heart_beat(); } else { /* We're done! */ break; } } DBG(("Done.")); pop_context(&econ); } static int time_left (int slot, int delay) { int current_slot = current_time & (CALLOUT_CYCLE_SIZE - 1); if (slot >= current_slot) { return (slot - current_slot) + delay * CALLOUT_CYCLE_SIZE; } else { return (slot - current_slot) + (delay + 1) * CALLOUT_CYCLE_SIZE; } } /* * Throw away a call out. First call to this function is discarded. * The time left until execution is returned. * -1 is returned if no call out pending. */ int remove_call_out (object_t * ob, const char * fun) { pending_call_t **copp, *cop; int delay; int i; if (!ob) return -1; DBG(("remove_call_out: /%s \"%s\"", ob->obname, fun)); for (i = 0; i < CALLOUT_CYCLE_SIZE; i++) { delay = 0; for (copp = &call_list[i]; *copp; copp = &(*copp)->next) { DBG((" Slot: %i\n", i)); delay += (*copp)->delta; if ((*copp)->ob == ob && strcmp((*copp)->function.s, fun) == 0) { cop = *copp; if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); DBG((" found.")); return time_left(i, delay); } } } DBG((" not found.")); return -1; } #ifdef CALLOUT_HANDLES int remove_call_out_by_handle (int handle) { pending_call_t **copp, *cop; int delay = 0; DBG(("remove_call_out_by_handle: handle: %i slot: %i", handle, handle & (CALLOUT_CYCLE_SIZE - 1))); for (copp = &call_list[handle & (CALLOUT_CYCLE_SIZE - 1)]; *copp; copp = &(*copp)->next) { delay += (*copp)->delta; if ((*copp)->handle == handle) { cop = *copp; if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); return time_left(handle & (CALLOUT_CYCLE_SIZE - 1), delay); } } return -1; } int find_call_out_by_handle (int handle) { pending_call_t *cop; int delay = 0; DBG(("find_call_out_by_handle: handle: %i slot: %i", handle, handle & (CALLOUT_CYCLE_SIZE - 1))); for (cop = call_list[handle & (CALLOUT_CYCLE_SIZE - 1)]; cop; cop = cop->next) { delay += cop->delta; if (cop->handle == handle) return time_left(handle & (CALLOUT_CYCLE_SIZE - 1), delay); } return -1; } #endif int find_call_out (object_t * ob, const char * fun) { pending_call_t *cop; int delay; int i; if (!ob) return -1; DBG(("find_call_out: /%s \"%s\"", ob->obname, fun)); for (i = 0; i < CALLOUT_CYCLE_SIZE; i++) { delay = 0; DBG((" Slot: %i", i)); for (cop = call_list[i]; cop; cop = cop->next) { delay += cop->delta; if (cop->ob == ob && strcmp(cop->function.s, fun) == 0) return time_left(i, delay); } } return -1; } int print_call_out_usage (outbuffer_t * ob, int verbose) { int i, j; pending_call_t *cop; for (i = 0, j = 0; j < CALLOUT_CYCLE_SIZE; j++) for (cop = call_list[j]; cop; cop = cop->next) i++; if (verbose == 1) { outbuf_add(ob, "Call out information:\n"); outbuf_add(ob, "---------------------\n"); outbuf_addv(ob, "Number of allocated call outs: %8d, %8d bytes\n", num_call, num_call * sizeof(pending_call_t)); outbuf_addv(ob, "Current length: %d\n", i); } else { if (verbose != -1) outbuf_addv(ob, "call out:\t\t\t%8d %8d (current length %d)\n", num_call, num_call * sizeof(pending_call_t), i); } return num_call * sizeof(pending_call_t); } #ifdef DEBUGMALLOC_EXTENSIONS #ifdef DEBUG void mark_call_outs() { pending_call_t *cop; int i; for (i = 0; i < CALLOUT_CYCLE_SIZE; i++) { for (cop = call_list[i]; cop; cop = cop->next) { if (cop->vs) cop->vs->extra_ref++; if (cop->ob) { cop->ob->extra_ref++; EXTRA_REF(BLOCK(cop->function.s))++; } else { cop->function.f->hdr.extra_ref++; } #ifdef THIS_PLAYER_IN_CALL_OUT if (cop->command_giver) cop->command_giver->extra_ref++; #endif } } } #endif #endif /* * Construct an array of all pending call_outs. Every item in the array * consists of 3 items (but only if the object not is destructed): * 0: The object. * 1: The function (string). * 2: The delay. */ array_t *get_all_call_outs() { int i, j, delay, tm; pending_call_t *cop; array_t *v; for (i = 0, j = 0; j < CALLOUT_CYCLE_SIZE; j++) for (cop = call_list[j]; cop; cop = cop->next) { object_t *ob = (cop->ob ? cop->ob : cop->function.f->hdr.owner); if (ob && !(ob->flags & O_DESTRUCTED)) i++; } v = allocate_empty_array(i); tm = current_time & (CALLOUT_CYCLE_SIZE-1); for (i = 0, j = 0; j < CALLOUT_CYCLE_SIZE; j++) { delay = 0; for (cop = call_list[j]; cop; cop = cop->next) { array_t *vv; object_t *ob; delay += cop->delta; ob = (cop->ob ? cop->ob : cop->function.f->hdr.owner); if (!ob || (ob->flags & O_DESTRUCTED)) continue; vv = allocate_empty_array(3); if (cop->ob) { vv->item[0].type = T_OBJECT; vv->item[0].u.ob = cop->ob; add_ref(cop->ob, "get_all_call_outs"); vv->item[1].type = T_STRING; vv->item[1].subtype = STRING_SHARED; vv->item[1].u.string = make_shared_string(cop->function.s); } else { outbuffer_t tmpbuf; svalue_t tmpval; tmpbuf.real_size = 0; tmpbuf.buffer = 0; tmpval.type = T_FUNCTION; tmpval.u.fp = cop->function.f; svalue_to_string(&tmpval, &tmpbuf, 0, 0, 0); vv->item[0].type = T_OBJECT; vv->item[0].u.ob = cop->function.f->hdr.owner; add_ref(cop->function.f->hdr.owner, "get_all_call_outs"); vv->item[1].type = T_STRING; vv->item[1].subtype = STRING_SHARED; vv->item[1].u.string = make_shared_string(tmpbuf.buffer); FREE_MSTR(tmpbuf.buffer); } vv->item[2].type = T_NUMBER; vv->item[2].u.number = time_left(j, delay); v->item[i].type = T_ARRAY; v->item[i++].u.arr = vv; /* Ref count is already 1 */ } } return v; } void remove_all_call_out (object_t * obj) { pending_call_t **copp, *cop; int i; for (i = 0; i < CALLOUT_CYCLE_SIZE; i++) { copp = &call_list[i]; while (*copp) { if ( ((*copp)->ob && (((*copp)->ob == obj) || ((*copp)->ob->flags & O_DESTRUCTED))) || (!(*copp)->ob && ((*copp)->function.f->hdr.owner == obj || !(*copp)->function.f->hdr.owner || (*copp)->function.f->hdr.owner->flags & O_DESTRUCTED)) ) { cop = *copp; if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); } else copp = &(*copp)->next; } } } void reclaim_call_outs() { pending_call_t *cop; int i; remove_all_call_out(0); /* removes call_outs to destructed objects */ #ifdef THIS_PLAYER_IN_CALL_OUT for (i = 0; i < CALLOUT_CYCLE_SIZE; i++) { cop = call_list[i]; while (cop) { if (cop->command_giver && (cop->command_giver->flags & O_DESTRUCTED)) { free_object(&cop->command_giver, "reclaim_call_outs"); cop->command_giver = 0; } cop = cop->next; } } #endif }