#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <types.h>
#include <stat.h>
#include <time.h>
#include <math.h>
/*#include "lint.h"*/
#include "config.h"
#include "lnode.h"
#include "interpret.h"
#include "object.h"
#include "wiz_list.h"
jmp_buf error_recovery_context;
int error_recovery_context_exists = 0;
/*
* The 'current_time' is updated at every heart beat with time(0).
*/
int current_time;
int remote_command = 0;
int call_depth = 0;
char *string_copy PROT ((char));
extern struct object *command_giver, *obj_list_destruct;
extern int num_player;
struct object *current_heart_beat;
struct object *current_reset;
/*extern long time ();*/
/* Should be */
extern time_t time ();
void call_heart_beat (), catch_alarm ();
void load_first_objects (), prepare_ipc (),
shutdowngame (), ed_cmd PROT ((char *)),
print_prompt (), move_current_reset (), call_out (),
destruct2 PROT ((struct object *));
extern int get_message PROT ((char *, int)), player_parser PROT ((char *)),
call_function_interactive PROT ((struct interactive *, char *)),
resort_free_list (), swap PROT ((struct object *));
extern int t_flag;
int time_to_call_heart_beat;
int comm_time_to_call_heart_beat = 0; /* this is set by interrupt, */
/* comm sets time_to_call_heart_beat sometime after */
/*
* There are global variables that must be zeroed before any execution.
* In case of errors, there will be a longjmp(), and the variables will
* have to be cleared explicitely. They are normally maintained by the
* code that use them.
*/
void
clear_state ()
{
extern struct lnode_2 *next_arg_list_to_use;
extern struct object *previous_ob, *current_inherit_ob;
extern int static_variable_flag, break_flag, break_level;
#ifdef TRACE
extern int trace_depth;
#endif
break_flag = 0;
break_level = 0;
static_variable_flag = 0;
next_arg_list_to_use = 0;
current_object = 0;
command_giver = 0;
previous_ob = 0;
current_inherit_ob = 0;
error_recovery_context_exists = 1;
#ifdef TRACE
trace_depth = 0;
#endif
}
void
logon (ob)
struct object *ob;
{
struct value *ret;
/*
* Set current object to ob, so that the static function "logon"
* can be called.
*/
current_object = ob;
call_depth = 0;
ret = apply ("logon", ob, 0);
if (ret == 0)
{
add_message ("prog %s:\n", ob->name);
fatal ("Could not find logon on the player %s\n", ob->name);
}
}
/*
* Take a player command and parse it.
* The command can also come from a NPC.
* Beware that 'str' can be modified and extended !
*/
int
parse_command (str, ob, from_command)
char *str;
struct object *ob;
int from_command;
{
struct object *save = command_giver;
int res;
if (from_command)
remote_command = 1;
command_giver = ob;
res = player_parser (str);
command_giver = save;
if (from_command)
remote_command = 0;
return res;
}
/*
* This is the backend. We will stay here for ever (almost).
*/
int eval_cost;
void
backend ()
{
char buff[2000];
extern void startshutdowngame ();
extern int game_is_being_shut_down;
extern int slow_shut_down_to_do;
free_all_values ();
(void) printf ("Loading init file %s\n", INIT_FILE);
load_first_objects ();
current_reset = obj_list;
(void) printf ("Seting up ipc.\n");
fflush (stdout);
prepare_ipc ();
(void) signal (SIGHUP, startshutdowngame);
if (!t_flag)
call_heart_beat ();
setjmp (error_recovery_context);
/*
* We come here after errors, and have to clear some global variables.
*/
clear_state ();
while (1)
{
eval_cost = 0;
remote_command = 0;
call_depth = 0;
remove_destructed_objects ();
if (game_is_being_shut_down)
shutdowngame ();
if (slow_shut_down_to_do)
{
int tmp = slow_shut_down_to_do;
slow_shut_down_to_do = 0;
slow_shut_down (tmp);
}
if (get_message (buff, sizeof buff))
{
void update_load_av PROT ((void));
update_load_av ();
/*
* Now we have a string from the player. This string can go to
* one of several places. If it is prepended with a '!', then
* it is an escape from the 'ed' editor, so we send it
* as a command to the parser.
* If any object function is waiting for an input string, then
* send it there.
* Otherwise, send the string to the parser.
* The player_parser() will find that current_object is 0, and
* then set current_object to point to the object that defines
* the command. This will enable such functions to be static.
*/
current_object = 0;
if (!command_giver->interactive)
fatal ("Non interactive player in main loop !\n");
if (command_giver->reset > 1)
command_giver->reset = 1;
if (buff[0] == '!' && command_giver->super)
parse_command (buff + 1, command_giver, 0);
else if (command_giver->ed_buffer)
ed_cmd (buff);
else if (call_function_interactive (command_giver->interactive, buff))
; /* Do nothing ! */
else
parse_command (buff, command_giver, 0);
/*
* Print a prompt if player is still here.
*/
if (command_giver->interactive)
print_prompt ();
}
command_giver = 0;
if (time_to_call_heart_beat)
call_heart_beat ();
free_all_values ();
}
}
/*
* Call all heart_beat() functions in all objects. Also call the next reset,
* and the call out.
* We do heart beats by moving each object done to the end of the heart beat
* list before we call its function, and always using the item at the head
* of the list as our function to call. We keep calling heart beats until
* a timeout or we have done num_heart_objs calls. It is done this way so
* that objects can delete heart beating objects from the list from within
* their heart beat without truncating the current round of heart beats.
*/
static struct object *hb_list = 0; /* head */
static struct object *hb_tail = 0; /* for sane wrap around */
static int num_hb_objs = 0; /* so we know when to stop! */
static int num_hb_calls = 0; /* stats */
static float perc_hb_probes = 100.0; /* decaying avge of how many complete */
void
call_heart_beat ()
{
struct object *ob, *hide_current = current_object, *hide_command = command_giver;
int num_done = 0;
time_to_call_heart_beat = 0; /* interrupt loop if we take too long */
comm_time_to_call_heart_beat = 0;
(void) signal (SIGALRM, catch_alarm);
alarm (2);
current_time = time (0l);
if ((num_player > 0) && hb_list)
{
num_hb_calls++;
while (hb_list && (!comm_time_to_call_heart_beat)
&& (num_done < num_hb_objs))
{
num_done++;
cycle_hb_list ();
ob = hb_tail; /* now at end */
if (ob->enable_heart_beat == 0)
fatal ("Heart beat not set in object on heart beat list!");
if (ob->swapped)
fatal ("Heart beat in swapped object.\n");
/* move ob to end of list, do ob */
if (ob->heart_beat == 0 || ob->reset == 0)
continue;
current_object = ob;
current_heart_beat = ob;
if (ob->enable_commands)
command_giver = ob;
else
command_giver = 0;
if (ob->wl)
ob->wl->heart_beats++;
eval_cost = 0;
call_depth = 0;
call_function ((struct lnode_def *) ob->heart_beat);
free_all_values ();
}
if (num_hb_objs)
perc_hb_probes = 100 * (float) num_done / num_hb_objs;
else
perc_hb_probes = 100.0;
}
command_giver = hide_command;
current_object = hide_current;
current_heart_beat = 0;
#if NUM_RESET_TO_SWAP > 0
move_current_reset ();
#endif
call_out (); /* some things depend on this, even without players! */
}
/*
* Take the first object off the heart beat list, place it at the end
*/
cycle_hb_list ()
{
struct object *ob;
if (!hb_list)
fatal ("Cycle heart beat list with empty list!");
if (hb_list == hb_tail)
return; /* 1 object on list */
ob = hb_list;
hb_list = hb_list->next_heart_beat;
hb_tail->next_heart_beat = ob;
hb_tail = ob;
ob->next_heart_beat = 0;
}
/*
* add or remove an object from the heart beat list; does the major check...
* If an object removes something from the list from within a heart beat,
* various pointers in call_heart_beat could be stuffed, so we must
* check current_heart_beat and adjust pointers.
*/
int
set_heart_beat (ob, to)
struct object *ob;
int to;
{
struct object *o = hb_list;
struct object *oprev = 0;
if (to)
to = 1;
while (o && o != ob)
{
if (!o->enable_heart_beat)
fatal ("Found disabled object in the active heart beat list!\n");
oprev = o;
o = o->next_heart_beat;
}
if (!o && ob->enable_heart_beat)
fatal ("Couldn't find enabled object in heart beat list!");
if (to == ob->enable_heart_beat)
return (0);
ob->enable_heart_beat = to;
if (to)
{
if (ob->next_heart_beat)
fatal ("Dangling pointer to next_heart_beat in object!");
ob->next_heart_beat = hb_list;
hb_list = ob;
if (!hb_tail)
hb_tail = ob;
num_hb_objs++;
}
else
{ /* remove all refs */
if (hb_list == ob)
hb_list = ob->next_heart_beat;
if (hb_tail == ob)
hb_tail = oprev;
if (oprev)
oprev->next_heart_beat = ob->next_heart_beat;
ob->next_heart_beat = 0;
num_hb_objs--;
}
return (1);
}
/*
* sigh. Another status function.
*/
int
heart_beat_status ()
{
char buf[20];
add_message ("Heart beat: objects: %d, starts: %d\n",
num_hb_objs, num_hb_calls);
sprintf (buf, "%.2f", perc_hb_probes);
add_message ("Percentage of HB calls completed last time: %s\n", buf);
return 0;
}
/*
* There is a file with a list of objects to be initialized at
* start up.
*/
void
load_first_objects ()
{
FILE *f;
char buff[1000];
char *p;
extern int e_flag;
#define vms_tms tbuffer
struct vms_tms tms1, tms2;
if (!load_object ("room/void"))
fatal ("Could not load the void.\n");
if (e_flag)
return;
f = fopen (INIT_FILE, "r");
if (f == 0)
return;
if (setjmp (error_recovery_context))
{
clear_state ();
add_message ("Anomaly in the fabric of world space.\n");
}
error_recovery_context_exists = 1;
times (&tms1);
while (1)
{
if (fgets (buff, sizeof buff, f) == NULL)
break;
if (buff[0] == '#')
continue;
p = strchr (buff, '\n');
if (p != 0)
*p = 0;
if (buff[0] == '\0')
continue;
(void) printf ("Preloading: %s", buff);
fflush (stdout);
eval_cost = 0;
call_depth = 0;
(void) load_object (buff);
free_all_values ();
#ifdef MALLOC_malloc
resort_free_list ();
#endif
times (&tms2);
#define tms_utime proc_user_time
#define tms_stime proc_system_time
(void) printf (" %.2f\n", (tms2.tms_utime - tms1.tms_utime +
tms2.tms_stime - tms1.tms_stime) / 60.0);
fflush (stdout);
tms1 = tms2;
}
error_recovery_context_exists = 0;
fclose (f);
}
/*
* catch alarm, set flag for comms code and heart_beat to catch.
* comms code sets time_to_call_heart_beat for the backend when
* it has completed the current round of player commands.
*/
void
catch_alarm ()
{
comm_time_to_call_heart_beat = 1;
}
/* This variable is used only for debugging. */
struct object *prev_reset;
void
move_current_reset ()
{
struct object *ob;
/*
* Store the object to reset in a local variable, and move current_reset
* to the next object that should be reset.
*/
if (current_reset == 0)
{
prev_reset = 0;
current_reset = obj_list;
return;
}
ob = current_reset;
if (ob == 0) /* Not likely, but possible */
return;
/*
* It can still happen that ob is destructed. This should not be
* possible, as current_reset never is allowed to point to a destructed
* object.
*/
if (ob->destructed)
fatal ("move_current_reset: destructed object 0x%x\n", ob);
prev_reset = current_reset;
current_reset = current_reset->next_all;
if (current_reset && current_reset->destructed)
fatal ("move_current_reset: current_reset points to destructed ob.\n");
if (!ob->destructed)
{
if (ob->last_reset < (NUM_RESET_TO_SWAP * 60 * RESET_TIME +
current_time) && !ob->swapped)
swap (ob);
}
if (current_reset && current_reset->destructed)
fatal ("current_reset destructed.\n");
}
/*
* All destructed objects are moved int a sperate linked list,
* and deallocated after program execution.
*/
void
remove_destructed_objects ()
{
struct object *ob, *next;
for (ob = obj_list_destruct; ob; ob = next)
{
next = ob->next_all;
destruct2 (ob);
}
obj_list_destruct = 0;
}
int
write_file (file, str)
char *file;
char *str;
{
FILE *f;
file = check_file_name (file, 1);
if (!file)
return;
f = fopen (file, "a");
if (f == 0)
return 0;
fwrite (str, strlen (str), 1, f);
fclose (f);
return 1;
}
int
file_size (file)
char *file;
{
struct stat st;
file = check_file_name (file, 0);
if (!file)
return -1;
if (stat (file, &st) == -1)
return -1;
if (S_IFDIR & st.st_mode)
return -2;
return st.st_size;
}
static double load_av = 0.0;
void
update_load_av ()
{
extern double consts[5];
extern int current_time;
static int last_time;
int i, n;
double c;
static acc = 0;
acc++;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < sizeof consts / sizeof consts[0])
c = consts[n];
else
c = exp (-n / 900.0);
load_av = c * load_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
static double compile_av = 0.0;
void
update_compile_av (lines)
int lines;
{
extern double consts[5];
extern int current_time;
static int last_time;
int i, n;
double c;
static acc = 0;
acc += lines;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < sizeof consts / sizeof consts[0])
c = consts[n];
else
c = exp (-n / 900.0);
compile_av = c * compile_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
struct value *
query_load_av ()
{
char buff[100];
sprintf (buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
return make_string (buff);
}