/* * This file implements fuses. See fuses.h for more. */ #include #include "time.h" #include "vars.h" #include "pline.h" #include "fuses.h" typedef struct fuse FUSE; /* * The internals of a fuse. time is when the fuse is to expire. fxn, * arg_li, and arg_vp are the callback and its arguments from the * addfuse_* call which created it. x is its index in the heap array * (see below). id is the fuse's ID. serial is its serial number * (see next_serial below). */ struct fuse { TIME time; void (*fxn)(long int, void *); long int arg_li; void *arg_vp; int x; int id; unsigned long int serial; } ; /* * Fuses are kept in a heap, with the next-expiring one at the top of * the heap. fuses is the heap (which holds pointers, not the FUSEs * themselves); fuses_a is the allocated size of fuses, with fuses_n * being the current operating size. */ static FUSE **fuses; static int fuses_a; static int fuses_n; /* * This is where fuse IDs are recorded. idtbl is the internal table * mapping from fuse IDs to FUSE pointers. idtbl is this table; ids_a * is its size. IDs for which a slot exists but which are not * currently in use are stored in freeids, with freeids_a being the * allocated size and freeids_n the current size. There is no ids_n; * all IDs in [0..ids_a) are either in use (with a FUSE in idtbl) or * free (with an entry in freeids). */ static FUSE **idtbl; static int ids_a; static int *freeids; static int freeids_a; static int freeids_n; /* * Every FUSE has a serial number. This has no operational * significance; it is mostly intended to be accessed by a debugger. * They are assigned sequentially; next_serial is the next one to be * used. */ static unsigned long int next_serial; /* * Initialize FUSE system state. */ void initfuses(void) { fuses = 0; fuses_a = 0; fuses_n = 0; idtbl = 0; ids_a = 0; freeids = 0; freeids_a = 0; freeids_n = 0; next_serial = 0; } /* * Free up an ID slot. It may be newly allocated or it may have been * in use until just now; we don't care which. */ static void free_id(int id) { idtbl[id] = 0; if (freeids_n >= freeids_a) freeids = realloc(freeids,(freeids_a=freeids_n+16)*sizeof(*freeids)); freeids[freeids_n++] = id; } /* * Return a new ID. If we have no free IDs, expand the ID table with * 16 new IDs and free the IDs, after which there will always be a * free ID available. */ static int getid(void) { int i; if (freeids_n < 1) { idtbl = realloc(idtbl,(ids_a+16)*sizeof(*idtbl)); for (i=0;i<16;i++) free_id(ids_a++); } return(freeids[--freeids_n]); } /* * Return true if f1 should come before f2 in heap ordering. This is * used as a comparison function for the heap of FUSEs. */ static int fuselt(FUSE *f1, FUSE *f2) { if (! f2->fxn) return(0); if (! f1->fxn) return(1); if (f1->time < f2->time) return(1); if (f1->time > f2->time) return(0); return(f1->serial < f2->serial); } /* * Bubble a heap member upwards. Used when adding a new item to the * heap. */ static void bubble_up(FUSE *f, unsigned int x) { unsigned int u; FUSE *t; while (x > 0) { u = (x-1) >> 1; if (! fuselt(f,fuses[u])) break; t = fuses[u]; fuses[x] = t; t->x = x; x = u; } f->x = x; fuses[x] = f; } /* * Bubble a heap member downwards. Used to recreate the heap invariant * after moving the last item to the root when removing an item. */ static void bubble_down(FUSE *f, unsigned int x) { unsigned int l; unsigned int r; unsigned int s; FUSE *t; while (1) { l = (x << 1) + 1; r = l + 1; if ((l < fuses_n) && fuselt(fuses[l],f)) { if ((r < fuses_n) && fuselt(fuses[r],f)) { s = fuselt(fuses[l],fuses[r]) ? l : r; } else { s = l; } } else { if ((r < fuses_n) && fuselt(fuses[r],f)) { s = r; } else { break; } } t = fuses[s]; t->x = x; fuses[x] = t; x = s; } f->x = x; fuses[x] = f; } /* * Given a newly-created (or otherwise set up but not in the heap) * FUSE, add it to the heap. */ static void addfuse(FUSE *f) { if (fuses_n >= fuses_a) fuses = realloc(fuses,(fuses_a=fuses_n+16)*sizeof(*fuses)); f->serial = next_serial ++; bubble_up(f,fuses_n++); } /* * API call: add a FUSE that expires at an absolute time. * (*fxn)(arg_li,arg_vp) will be called at time t. */ int addfuse_abs(void (*fxn)(long int, void *), long int arg_li, void *arg_vp, TIME t) { FUSE *f; f = malloc(sizeof(FUSE)); f->time = t; f->fxn = fxn; f->arg_li = arg_li; f->arg_vp = arg_vp; f->id = getid(); idtbl[f->id] = f; addfuse(f); return(f->id); } /* * API call: add a FUSE that expires at a relative time, where the * delta is given as a TIME. (*fxn)(arg_li,arg_vp) will be called at * time now+dt. */ int addfuse_rel(void (*fxn)(long int, void *), long int arg_li, void *arg_vp, TIME dt) { return(addfuse_abs(fxn,arg_li,arg_vp,curtime+dt)); } /* * API call: cancel a FUSE, given the ID returned by the addfuse_* call * that created it. This doesn't actually destroy the FUSE; it just * sets its callback to nil, so that when it expires it doesn't do * anything. This is for robustness; since everything in the game * happens inside FUSE callbacks, we have to handle cases like a * FUSE's callback trying to cancel that same FUSE (perhaps * indirectly via something like monster death). */ void cancelfuse(int id) { FUSE *f; if ((id < 0) || (id >= ids_a)) panic("cancelfuse impossible id"); f = idtbl[id]; if (f->id != id) panic("cancelfuse id wrong"); if ((f->x < 0) || (f->x >= fuses_n)) panic("cancelfuse impossible index"); if (fuses[f->x] != f) panic("cancelfuse index wrong"); f->fxn = 0; bubble_up(f,f->x); } /* * API call: expire the next-expiring fuse: set curtime and call its * callback (if it has one; see the comment on cancelfuse, above). * Remove it from the heap before making the callback, but don't free * its ID until afterwards, in case the callback tries to cancel it. */ void tick(void) { FUSE *f; if (fuses_n < 1) abort(); f = fuses[0]; bubble_down(fuses[--fuses_n],0); if (f->fxn) { curtime = f->time; (*f->fxn)(f->arg_li,f->arg_vp); } free_id(f->id); free(f); }