OSDN Git Service

日本語版
[nazghul-jp/nazghul-jp.git] / src / session.c
1 //
2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
4 //
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
8 // any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13 // more details.
14 //
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
18 //
19 // Gordon McNutt
20 // gmcnutt@users.sourceforge.net
21 //
22
23 #include "session.h"
24
25 #include "../config.h"
26 #include "character.h"
27 #include "object.h"
28 #include "sprite.h"
29 #include "terrain.h"
30 #include "place.h"
31 #include "images.h"
32 #include "Party.h"
33 #include "ptable.h"
34 #include "file.h"
35 #include "player.h"
36 #include "sky.h"
37 #include "map.h"
38 #include "cursor.h"
39 #include "Arms.h"
40 #include "Field.h"
41 #include "occ.h"
42 #include "species.h"
43 #include "sched.h"
44 #include "Reagent.h"
45 #include "screen.h"
46 #include "vehicle.h"
47 #include "formation.h"
48 #include "combat.h"
49 #include "Container.h"
50 #include "clock.h"
51 #include "wind.h"
52 #include "foogod.h"
53 #include "terrain_map.h" // dbg
54 #include "dtable.h"
55 #include "wq.h"
56 #include "cfg.h"
57 #include "skill.h"
58 #include "skill_set.h"
59
60 #include <assert.h>
61 #include <ctype.h>              // isspace()
62 #include <errno.h>
63 #include <kern.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <time.h>
67 #include <SDL_image.h>
68 #include <unistd.h>
69 #include <sys/stat.h>
70 //#include <sys/mman.h>
71 #include <stdarg.h>
72 #include <SDL.h>            // for SDL_GetTicks()
73
74 //#include "scheme.h"
75 #include "scheme-private.h"
76
77 struct data_obj_entry {
78         struct list list;
79         void *obj;
80         int refcount;
81         void (*dtor)(void*);
82         void (*save)(save_t *save, void *obj);
83         void (*start)(void *obj);
84 };
85
86 struct session_hook_entry {
87         struct list list;
88         struct closure *proc;
89         pointer args;
90 };
91
92 struct session *Session = 0;
93 int load_errs = 0;
94 int save_errs = 0;
95
96 static void session_init_hooks(struct session *session);
97 static void session_cleanup_hooks(struct session *session);
98 static void session_cleanup_queries(struct session *session);
99
100 /* Redefine the session hook macro to turn its arg into a string, then #include
101  * the list of hooks directly into an array of string pointers. */
102 #undef SESSION_DECL_HOOK
103 #define SESSION_DECL_HOOK(id) #id
104 static const char * session_hook_str[] = {
105 #       include "session_hooks.h"
106 };
107
108 static char session_last_error[128] = { 0 };
109
110 void load_err(const char *fmt, ...)
111 {
112         load_err_inc();
113         warn("load_err: ");
114         va_list args;
115         va_start(args, fmt);
116         vwarn(fmt, args);
117         va_end(args);
118         warn("\n");
119
120         /* duplicate it to the global string error */
121         va_start(args, fmt);
122         vsnprintf(session_last_error, sizeof(session_last_error),
123                   fmt, args);
124         va_end(args);
125         
126 }
127
128 void rt_err(const char *fmt, ...)
129 {
130         load_err_inc();
131         warn("runtime error: ");
132         va_list args;
133         va_start(args, fmt);
134         vwarn(fmt, args);
135         va_end(args);
136         warn("\n");
137
138         consolePrint("\n*** script error! ***\n");
139 }
140
141
142
143 void save_err(const char *fmt, ...)
144 {
145         save_err_inc();
146         warn("save_err: ");
147         va_list args;
148         va_start(args, fmt);
149         vwarn(fmt, args);
150         va_end(args);
151         warn("\n");
152 }
153
154 static void data_obj_entry_unref(struct data_obj_entry *entry)
155 {
156         assert(entry->refcount >= 1);
157         entry->refcount--;
158         if (! entry->refcount) {
159                 free(entry);
160         }
161 }
162
163 void *session_add(struct session *session, void *obj, 
164                   void (*dtor)(void *),
165                   void (*save)(save_t *, void *),
166                   void (*start)(void *)
167         )
168 {
169         struct data_obj_entry *entry;
170
171         entry = (struct data_obj_entry *)malloc(sizeof(*entry));
172         assert(entry);
173         list_init(&entry->list);
174         entry->obj = obj;
175         entry->refcount = 1;
176         entry->dtor = dtor;
177         entry->save = save;
178         entry->start = start;
179
180         /* Note: it's important to always add to the tail of the list. This
181          * ensures that the objects in a session will be saved in the same
182          * order they are loaded. The order can matter in certain cases (e.g.,
183          * include files loaded at the front of the session). */
184         list_add_tail(&session->data_objects, &entry->list);
185         return entry;
186 }
187
188 void session_rm(struct session *session, void *handle)
189 {
190         struct data_obj_entry *entry;
191         entry = (struct data_obj_entry *)handle;
192         list_remove(&entry->list);
193         data_obj_entry_unref(entry);
194 }
195
196 static void session_save_crosshair(save_t *save, struct session *session)
197 {
198         save->write(save, "(kern-set-crosshair %s)\n", 
199                 session->crosshair_type->getTag());
200 }
201
202 static void session_save_damage_sprite(save_t *save, struct session *session)
203 {
204         save->write(save, "(kern-set-damage-sprite %s)\n", 
205                     sprite_get_tag(session->damage_sprite));
206 }
207
208 static void session_save_clock(save_t *save, struct session *session)
209 {
210         save->write(save, "(kern-set-clock %d %d %d %d %d %d)\n",
211                 session->clock.year,
212                 session->clock.month,
213                 session->clock.week,
214                 session->clock.day,
215                 session->clock.hour,
216                 session->clock.min);
217 }
218
219 static void session_save_time_accel(save_t *save, struct session *session)
220 {
221         save->write(save, "(kern-set-time-accel %f)\n",
222                     session->time_accel);
223 }
224
225 static void session_save_turn_count(save_t *save, struct session *session)
226 {
227         save->write(save, "(kern-set-turn-count %d)\n", 
228                     session->turn_count);
229 }
230
231 struct session *session_new(void *interp)
232 {
233         struct session *session = (struct session*)calloc(1, sizeof(*session));
234         assert(session);
235         list_init(&session->data_objects);        
236         list_init(&session->terrains);
237         session->interp = interp;
238         session->status_mode = ShowParty;
239         session->los = "angband";
240         sky_init(&session->sky);
241         magic_init(&session->magic);
242         list_init(&session->tickq);
243         list_init(&session->turnq);
244         node_init(&session->sched_chars);
245         list_init(&session->blenders);
246         list_init(&session->skills);
247         list_init(&session->skill_sets);
248         session->time_accel = 1;
249         session_init_hooks(session);
250         return session;
251 }
252
253 void session_del(struct session *session)
254 {
255         struct list *elem;
256         struct data_obj_entry *entry;
257
258         /* ref the player party so we can control when it gets destroyed, so we
259          * know when to zero out the global */
260         if (session->player) {
261                 obj_inc_ref(session->player);
262         }
263
264         freezer_del();
265
266         /* stop sound so sound entries will be purged */
267         sound_haltall();
268         
269         /* Cleanup the data objects */
270         elem = session->data_objects.next;
271         int count = 0;
272         while (elem != &session->data_objects) {
273                 entry = list_entry(elem, struct data_obj_entry, list);
274                 elem = elem->next;
275                 entry->refcount++; /* keep a ref while dtor runs */
276                 entry->dtor(entry->obj);
277                 data_obj_entry_unref(entry); /* now release ref */
278                 count++;
279         }
280
281         /* Cleanup the terrains */
282         elem = session->terrains.next;
283         while (elem != &session->terrains) {
284                 struct terrain *terrain = list_entry(elem, struct terrain, 
285                                                      session_list);
286                 elem = elem->next;
287                 list_remove(&terrain->session_list);
288                 terrain_del(terrain);
289         }
290
291         if (session->crosshair)
292                 obj_dec_ref(session->crosshair);
293         sky_end_session(&session->sky);
294         magic_end_session(&session->magic);
295         if (session->ptable)
296                 ptable_del(session->ptable);
297         if (session->dtable)
298                 dtable_del(session->dtable);
299
300         /* Clean up the turn work queue */
301         elem = session->turnq.next;
302         while (elem != &session->turnq) {
303                 struct wq_job *job = list_entry(elem, struct wq_job, list);
304                 elem = elem->next;
305                 wq_job_del(job);
306         }
307
308         /* Clean up the tick work queue */
309         elem = session->tickq.next;
310         while (elem != &session->tickq) {
311                 struct wq_job *job = list_entry(elem, struct wq_job, list);
312                 elem = elem->next;
313                 wq_job_del(job);
314         }
315
316         session_cleanup_hooks(session);
317         session_cleanup_queries(session);
318
319         /* Ensure that nothing is referencing the player party (except perhaps
320          * its vehicle, which will be cleaned up with the party). Note: if
321          * we're aborting a failed load then the player may not exist. */
322         if (session->player) {
323                 assert((1==session->player->refcount)
324                        || ((2==session->player->refcount)
325                            && session->player->getVehicle()));
326
327                 /* Now zilch the global player party */
328                 obj_dec_ref(session->player);
329                 session->player = 0;
330         }
331
332         /* clean up the list of skills */
333         elem = session->skills.next;
334         while (elem != &session->skills) {
335                 struct skill *skill = list_entry(elem, struct skill, list);
336                 elem = elem->next;
337                 list_remove(&skill->list);
338                 skill_unref(skill);
339         }
340
341         /* clean up the list of skill sets */
342         elem = session->skill_sets.next;
343         while (elem != &session->skill_sets) {
344                 struct skill_set *skset 
345                         = list_entry(elem, struct skill_set, list);
346                 elem = elem->next;
347                 list_remove(&skset->list);
348                 skill_set_unref(skset);
349         }
350
351         /* cleanup the interpreter */
352         if (session->interp) {
353                 scheme_deinit((scheme*)session->interp);
354                 free(session->interp);
355         }
356
357         /* Check for memory leaks. Character dtors should have removed
358          * themselves from the sched_chars list if they were on it. */
359         if (! node_list_empty(&session->sched_chars)) {
360                 fprintf(stderr, 
361                         "warn: session sched_chars list non-empty\n");
362         }
363
364         free(session);
365 }
366
367 int session_load(char *filename)
368 {
369         scheme *sc;
370         FILE *file = NULL;
371         struct session *old_session;
372         int t1, t2;
373         struct list *elem;
374
375         /* Remember the old session in case we have to bail out. */
376         old_session = Session;
377
378         /* Clear any leftover load errors. */
379         load_err_clear();
380
381         /* Open the load file (this might come from the include dir or the save
382          * dir, so the caller must pass in the complete pathname). */
383         file = file_open(filename, "r");
384         if (! file) {
385                 load_err("could not open script file '%s' for reading: %s",
386                            filename, strerror(errno));
387                 return -1;
388         }
389
390         /* Create a new interpreter. */
391         if (! (sc = kern_init())) {
392                 load_err("could not create interpreter");
393                 fclose(file);
394                 return -1;
395         }
396
397         /* Create a new current sesssion. */
398         Session = session_new(sc);
399         assert(Session);
400
401         /* Load the file and close it. */
402         t1 = SDL_GetTicks();
403         scheme_load_named_file(sc, file, filename);
404         t2 = SDL_GetTicks();
405         info("%d ms to load\n", (t2 - t1));
406
407         fclose(file);
408
409         /* Check for any errors during loading. */
410         if (load_err_any()) {
411
412                 /* Check if the problem is version obsolescence. */
413                 if (Session->major < MIN_SCRIPT_MAJOR
414                     || Session->minor < MIN_SCRIPT_MINOR
415                     || Session->release < MIN_SCRIPT_RELEASE) {
416                         snprintf(session_last_error,
417                                  sizeof(session_last_error),
418                                  "The save file format is version %u.%u.%u, "
419                                  "but this release of the engine requires "
420                                  "version %u.%u.%u or better. You might try "
421                                  "an older version of the engine with this "
422                                  "file.",
423                                  Session->major, Session->minor, 
424                                  Session->release, MIN_SCRIPT_MAJOR, 
425                                  MIN_SCRIPT_MINOR, MIN_SCRIPT_RELEASE);
426                 }
427
428                 session_del(Session);
429                 Session = old_session;
430                 return -1;
431         }
432
433         /* Check for stuff that needs to be there for the new session to
434          * work. Start with some globals. */
435         if (! Session->crosshair_type) {
436                 load_err("no crosshair object (use kern-set-crosshair)");
437         } else {
438                 Session->crosshair = new Cursor();
439                 Session->crosshair->init(Session->crosshair_type);
440                 obj_inc_ref(Session->crosshair);
441         }
442         if (! Session->damage_sprite) {
443                 load_err("no damage sprite (use kern-set-cursor)");
444         }
445         if (! Session->clock.set) {
446                 load_err("clock not set (use kern-set-clock)");
447         }
448         if (! Session->ptable) {
449                 load_err("passability table not set (use kern-set-ptable)");
450         }
451         if (! Session->dtable) {
452                 load_err("diplomacy table not set (use kern-set-dtable)");
453         }
454
455         if (! Session->player) {
456                 load_err("no player party");
457         }
458         if (Session->player->getSize() == 0) {
459                 load_err("player party empty");
460         }
461
462
463         /* Check for any errors. If there are any then destroy the new session
464          * and return the old one. Otherwise destroy the old session and return
465          * the new one. */
466         if (load_err_any()) {
467                 session_del(Session);
468                 scheme_deinit(sc);
469                 free(sc);
470                 Session = old_session;
471                 return -1;
472         }
473
474         /* No errors, so it's safe to delete the old session. */
475         if (old_session) {
476                 session_del(old_session);
477         }
478
479         combat_reset_state();
480         
481         Session->player->startSession();
482
483         /* Now setup stuff that with known defaults. */
484         statusSetMode(Session->status_mode);
485         mapSetLosStyle(Session->los);
486
487         /* Run through all the objects in the world, initializing their
488          * effects */
489         list_for_each(&Session->data_objects, elem) {
490                 struct data_obj_entry *entry;
491                 entry = list_entry(elem, struct data_obj_entry, list);
492                 if (entry->start) {
493                         entry->start(entry->obj);
494                 }
495         }     
496  
497                 /* need to start all the objects in the freezer too */
498                 freezer_start_contents();
499
500         sky_start_session(&Session->sky, 
501                           NULL != Place && ! Place->underground);
502
503
504         // This is also called from place_enter(), which is called when the
505         // game first starts up but apparently not necessarily on a
506         // reload. Should be safe to call more than once, I think.
507         //session_synch_sched_chars(Session);
508  
509         /* Paint all the windows for the first time in the new session. */
510         screenErase(0);
511         screen_repaint_frame();
512         foogodRepaint();
513         consoleRepaint();
514         statusRepaint();
515
516         /* show the sun, moon and wind status */
517         sky_advance(&Session->sky, 
518                     NULL != Place && ! Place->underground);
519
520         windRepaint();
521
522         session_run_hook(Session, session_start_hook, "p", Session->player);
523         
524         return 0;
525 }
526
527 #define SAVE_INDENT_WIDTH 2
528 static void save_vwrite(struct save *save, const char *fmt, va_list args)
529 {
530         if (save->indent)
531                 fprintf(save->file, "%*c", save->indent, ' ');
532         vfprintf(save->file, fmt, args);
533 }
534
535 static void save_write(save_t *save, const char *fmt, ...)
536 {
537         va_list args;
538         va_start(args, fmt);
539         save_vwrite(save, fmt, args);
540         va_end(args);
541 }
542
543 static void save_append(save_t *save, const char *fmt, ...)
544 {
545         va_list args;
546         va_start(args, fmt);
547         vfprintf(save->file, fmt, args);
548         va_end(args);    
549 }
550
551 static void save_enter(save_t *save, const char *fmt, ...)
552 {
553         va_list args;
554         va_start(args, fmt);
555         save_vwrite(save, fmt, args);
556         va_end(args);
557         save->indent += SAVE_INDENT_WIDTH;
558 }
559
560 static void save_exit(save_t *save, const char *fmt, ...)
561 {
562         va_list args;
563         va_start(args, fmt);
564         save->indent -= SAVE_INDENT_WIDTH;
565         assert(save->indent >= 0);
566         save_vwrite(save, fmt, args);
567         va_end(args);        
568 }
569
570 save_t *save_new(FILE *file)
571 {
572         save_t *save;
573
574         save = (save_t*)calloc(1, sizeof(*save));
575         assert(save);
576
577         save->file = file;
578         // SAM: Where is indent ever initialized???
579         save->write = save_write;
580         save->enter = save_enter;
581         save->exit = save_exit;
582         save->append = save_append;
583
584         return save;
585 }
586
587 void save_del(save_t *save)
588 {
589         if (save->indent) {
590                 warn("save file indentation left at %d; possible "\
591                      "unterminated block in save file", save->indent);
592         }
593         fflush(save->file);
594         free(save);
595 }
596
597 int session_save(char *fname)
598 {
599         FILE *file = 0;
600         struct list *elem;
601         save_t *save;
602         int object_saves = 0;
603
604         file = file_open_in_save_dir(fname, "w");
605         if (! file) {
606                 warn("session_save: could not open %s: %s\n", fname,
607                      file_get_error());
608                 return -1;
609         }
610
611         save = save_new(file);
612
613         /* Advance the session ID each time we save so that objects can know
614          * that they need to save themselves. */
615         Session->session_id++;
616         save->session_id = Session->session_id;
617
618         /* Save the header. */
619         save->write(save, ";; %s -- a nazghul session file\n", fname);
620         /* Note: below does not work, and causes false negs with regression
621          * tests: */
622         /* save->write(save, ";; Created %s\n", ctime(&timep)); */
623
624         /* Save the standard file. */
625         save->write(save, ";; Load the standard definitions file\n");
626         save->write(save, "(load \"naz.scm\")\n");
627         save->write(save, "\n");
628
629         /* Write the (new) version */
630         save->write(save, "(kern-script-version \"%s\")\n", PACKAGE_VERSION);
631
632         /* Generate the first part of the progress bar code. Use the number of
633          * data objects which will save themselves, plus the number of load
634          * files, as the limit. */
635         list_for_each(&Session->data_objects, elem) {
636                 struct data_obj_entry *entry;
637                 entry = list_entry(elem, struct data_obj_entry, list);
638                 if (entry->save) {
639                         object_saves++;
640                 }
641         }
642
643         save->write(save, ";; Progress bar\n");
644         save->write(save, "(kern-progress-bar-start \"Æɤ߹þ¤ßÃæ\" %d)\n", 
645                     Session->num_kern_includes + object_saves);
646         
647         /* Generate code to advance the progress bar as each file is loaded. */
648         save->write(save, "(define original-load load)  "
649                     "(define (load file) "
650                     "(kern-progress-bar-advance 1) "
651                     "(original-load file))\n");
652
653         /* Save all the saveable objects. */
654         list_for_each(&Session->data_objects, elem) {
655                 struct data_obj_entry *entry;
656                 entry = list_entry(elem, struct data_obj_entry, list);
657                 if (entry->save) {
658                         entry->save(save, entry->obj);
659                         if (save_err_any()) {
660                                 warn("Aborting save to %s due to errors\n", 
661                                      fname);
662                                 break;
663                         }
664
665                         /* Generate code to advance the progress bar as each
666                          * object finishes loading. */
667                         save->write(save, "(kern-progress-bar-advance 1)\n");
668                 }
669
670         }
671         /* Object freezer */
672         save->write(save, ";;--------------\n");
673         save->write(save, ";; ObjectFreezer\n");
674         save->write(save, ";;--------------\n");
675         freezer_save(save);
676
677         /* Save all the special-case stuff... */
678         save->write(save, ";;--------------\n");
679         save->write(save, ";; Miscellaneous\n");
680         save->write(save, ";;--------------\n");
681         session_save_damage_sprite(save, Session);
682         session_save_crosshair(save, Session);
683         session_save_clock(save, Session);
684         session_save_time_accel(save, Session);
685         session_save_turn_count(save, Session);
686         dtable_save(Session->dtable, save);
687         sky_save(&Session->sky, save);
688         windSave(save);
689
690         /* Save the flags */
691         save->write(save, "(kern-add-reveal %d)\n", Session->reveal.duration);
692         save->write(save, "(kern-add-quicken %d)\n", 
693                     Session->quicken.duration);
694         save->write(save, "(kern-add-time-stop %d)\n", 
695                     Session->time_stop.duration);
696         save->write(save, "(kern-add-magic-negated %d)\n", 
697                     Session->magic_negated.duration);
698         save->write(save, "(kern-add-xray-vision %d)\n", 
699                     Session->xray.duration);
700
701         /* save the work queues */
702         /* NOTE: don't see how we can, since work queue jobs use a void
703          * pointer. We could also require a save callback pointer in the job,
704          * but usually the data is a C pointer which won't be valid on reload,
705          * so I don't know what the save callback can do to help. */
706         /*         session_save_wq(&session->turnq); */
707         /*         session_save_wq(&session->tickq); */
708
709         /* Finish progress bar code */
710         save->write(save, "(kern-progress-bar-finish)\n");
711
712         save_del(save);
713         fclose(file);                
714         return 0;
715 }
716
717 struct node *session_add_sched_char(struct session *session,
718                                     class Character *npc)
719 {
720         struct node *node = node_new(npc);
721         node_add(&session->sched_chars, node);
722         return node;
723 }
724
725 void session_rm_sched_char(struct node *node)
726 {
727         /* beware of doing this while iterationg over the sched_chars list */
728         node_remove(node);
729         node_unref(node);
730 }
731
732 void session_synch_sched_chars(struct session *session)
733 {
734         struct node *node = node_next(&session->sched_chars);
735         while (node != &session->sched_chars) {
736                 class Character *npc = (class Character*)node->ptr;
737                 node = node_next(node);
738                 npc->synchronize();
739                 if (npc->isCharmed())
740                         npc->unCharm();
741         }
742 }
743
744 void session_intro_sched_chars(struct session *session)
745 {
746         struct node *node = node_next(&session->sched_chars);
747         while (node != &session->sched_chars) {
748                 class Character *npc = (class Character*)node->ptr;
749                 npc->introduce();
750                 node = node_next(node);
751         }        
752 }
753
754 static void session_init_hooks(struct session *session)
755 {
756         int id;
757
758         for (id = 0; id < NUM_HOOKS; id++) {
759                 list_init(&session->hook_table[id]);
760         }
761 }
762
763 void session_run_hook(struct session *session, session_hook_id_t id, const char *fmt, ...)
764 {
765         struct list *lptr, *head;
766
767         assert(id < NUM_HOOKS);
768         head = &session->hook_table[id];
769
770         list_for_each(head, lptr) {
771                 struct session_hook_entry *entry = list_entry(lptr, struct session_hook_entry, list);
772                 va_list args;
773                 va_start(args, fmt);
774                 closure_execvl(entry->proc, fmt, args, entry->args);
775                 va_end(args);
776         }
777         
778         
779 }
780
781 void *session_add_hook(struct session *session, session_hook_id_t id, struct closure *proc, pointer args)
782 {
783         struct list *head;
784         struct session_hook_entry *entry;
785
786         assert(id < NUM_HOOKS);
787         head = &session->hook_table[id];
788
789         if (!(entry = (struct session_hook_entry*)calloc(1, sizeof(*entry)))) {
790                 return 0;
791         }
792
793         entry->proc = proc;
794         closure_ref(proc);
795         entry->args = args;
796         proc->sc->vptr->protect(proc->sc, args);
797
798         list_add_tail(&session->hook_table[id], &entry->list);
799         return entry;
800 }
801
802 void session_rm_hook(struct session *session, session_hook_id_t id, pointer code)
803 {
804         struct list *head, *lptr;
805         struct session_hook_entry *entry;
806
807         assert(id < NUM_HOOKS);
808         head = &session->hook_table[id];
809         lptr = head->next;
810         while (lptr != head) {
811             entry = list_entry(lptr, struct session_hook_entry, list);
812             struct closure *proc = entry->proc;
813             if (proc->code == code) {
814                 list_remove(lptr);
815                 scheme *sc = proc->sc;
816                 sc->vptr->unprotect(sc, entry->args);
817                 closure_unref(proc);
818                 free(entry);
819                 return;
820             }
821             lptr = lptr->next;
822         }
823 }
824
825 static void session_cleanup_hooks(struct session *session)
826 {
827         int i;
828         for (i = 0; i < NUM_HOOKS; i++) {
829                 struct list *head = &session->hook_table[i];
830                 struct list *lptr = head->next;
831                 while (lptr != head) {
832                         struct session_hook_entry *entry = list_entry(lptr, struct session_hook_entry, list);
833                         lptr = lptr->next;
834                         list_remove(&entry->list);
835                         entry->proc->sc->vptr->unprotect(entry->proc->sc, entry->args);
836                         closure_unref(entry->proc);
837                         free(entry);
838                 }
839         }
840 }
841
842 int session_run_query(struct session *session, session_query_id_t id, const char *fmt, ...)
843 {
844         assert(id < NUM_QUERIES);
845         struct closure *proc = session->query_table[id];
846         va_list args;
847
848         if (proc) {
849                 pointer result;
850                 va_start(args, fmt);
851                 result = closure_execv(proc, fmt, args);
852                 va_end(args);
853                 return closure_translate_result(proc->sc, result);
854         }
855         
856         return 0;
857 }
858
859 void session_add_query(struct session *session, session_query_id_t id, struct closure *proc)
860 {
861         assert(id < NUM_QUERIES);
862
863         /* out with the old */
864         if (session->query_table[id]) {
865                 closure_unref(session->query_table[id]);
866                 session->query_table[id] = NULL;
867         }
868         
869         /* in with the new */
870         if (proc) {
871                 closure_ref(proc);
872                 session->query_table[id] = proc;
873         }
874 }
875
876 static void session_cleanup_queries(struct session *session)
877 {
878         int i;
879         for (i = 0; i < NUM_QUERIES; i++) {
880                 if (session->query_table[i]) {
881                         closure_unref(session->query_table[i]);
882                         session->query_table[i] = NULL;
883                 }
884         }
885 }
886
887 const char *session_hook_id_to_str(session_hook_id_t id)
888 {
889         assert(id < NUM_HOOKS);
890         return session_hook_str[id];
891 }
892
893 session_hook_id_t session_str_to_hook_id(char *str)
894 {
895         int id;
896         for (id = 0; id < NUM_HOOKS; id++) {
897                 if (! strcmp(session_hook_str[id], str)) {
898                         break;
899                 }
900         }
901         return (session_hook_id_t)id;
902 }
903
904 char *session_get_last_error(void)
905 {
906         return session_last_error;
907 }
908
909 void session_eval(struct session *session, char *buf)
910 {
911         scheme_load_string((scheme *)(session->interp), (const char*)buf);
912 }