#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "comm.h"
#include "stralloc.h"
#include "exec.h"
#include "wiz_list.h"
extern int trace_level;
/*
* 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
struct call {
int delta;
union {
struct {
char *name;
struct object *ob;
} named;
struct svalue lambda;
} function;
int is_lambda;
struct svalue v;
struct call *next;
struct object *command_giver;
};
static struct call *call_list=0, *call_list_free=0;
static int num_call;
/*
* Free a call out structure.
*/
static void free_call(cop)
struct call *cop;
{
if (cop->v.type == T_LVALUE) {
int i;
struct svalue *v;
i = cop->v.x.num_arg;
v = cop->v.u.lvalue;
do {
free_svalue(v++);
} while (--i);
xfree((char *)cop->v.u.lvalue);
} else {
free_svalue(&cop->v);
}
cop->next = call_list_free;
if (cop->is_lambda) {
free_closure(&cop->function.lambda);
} else {
free_string(cop->function.named.name);
free_object(cop->function.named.ob, "free_call");
}
if (cop->command_giver)
free_object(cop->command_giver, "free_call");
call_list_free = cop;
}
static INLINE void free_used_call(cop)
struct call *cop;
{
cop->next = call_list_free;
if (cop->command_giver)
free_object(cop->command_giver, "free_call");
call_list_free = cop;
}
/*
* Setup a new call out.
*/
void new_call_out(arg, num_arg)
struct svalue *arg;
int num_arg;
{
int delay;
struct call *cop, **copp, *cop2;
if (!call_list_free) {
int i;
call_list_free =
(struct call *)permanent_xalloc(CHUNK_SIZE * sizeof (struct call));
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(arg[0].type == T_CLOSURE) {
cop->function.lambda = arg[0];
cop->is_lambda = 1;
} else {
struct object *ob;
if (arg[0].x.string_type == STRING_SHARED) {
cop->function.named.name = arg[0].u.string;
} else {
cop->function.named.name = make_shared_string(arg[0].u.string);
if (arg[0].x.string_type == STRING_MALLOC)
xfree(arg[0].u.string);
}
cop->function.named.ob = ob = current_object;
add_ref(ob, "call_out");
cop->is_lambda = 0;
}
cop->command_giver = command_giver; /* save current player context */
if (command_giver)
add_ref(command_giver, "new_call_out"); /* Bump its ref */
if (num_arg > 1) {
struct svalue *v, *w;
v = (struct svalue *)xalloc(num_arg * sizeof *v);
cop->v.type = T_LVALUE;
cop->v.x.num_arg = num_arg;
cop->v.u.lvalue = v;
w = &arg[2];
do {
if (w->type == T_LVALUE) {
/* We could give an error here, but it would be more work */
free_svalue(w);
w->type = T_NUMBER;
w->u.number = 0;
}
transfer_svalue_no_free(v++, w++);
} while (--num_arg);
} else if (num_arg) {
if (arg[2].type != T_LVALUE) {
transfer_svalue_no_free(&cop->v, &arg[2]);
} else {
free_svalue(&arg[2]);
cop->v.type = T_NUMBER;
cop->v.u.number = 0;
}
} else {
cop->v.type = T_NUMBER;
cop->v.u.number = 0;
}
delay = arg[1].u.number;
if (delay < 1)
delay = 1;
for (copp = &call_list; cop2 = *copp; copp = &cop2->next) {
int delta;
if ((delta = cop2->delta) >= delay) {
cop2->delta -= delay;
cop->delta = delay;
cop->next = *copp;
*copp = cop;
return;
}
delay -= delta;
}
*copp = cop;
cop->delta = delay;
cop->next = 0;
}
/*
* 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() {
extern struct object *command_giver;
extern struct object *current_interactive;
extern int current_time;
extern int tracedepth;
extern struct svalue *inter_sp;
extern struct wiz_list default_wizlist_entry;
extern int32 initial_eval_cost, eval_cost, assigned_eval_cost;
static int last_time;
static struct call *current_call_out; /* don't set back with longjmp() */
struct error_recovery_info error_recovery_info;
struct svalue *sp;
if (call_list == 0) {
last_time = current_time;
return;
}
if (last_time == 0)
last_time = current_time;
current_interactive = 0;
call_list->delta -= current_time - last_time;
last_time = current_time;
error_recovery_info.last = error_recovery_pointer;
error_recovery_info.type = ERROR_RECOVERY_BACKEND;
error_recovery_pointer = &error_recovery_info;
if (setjmp(error_recovery_info.context)) {
extern void clear_state();
struct call *cop;
struct object *ob;
struct wiz_list *user;
clear_state();
debug_message("Error in call out.\n");
cop = current_call_out;
if (cop->is_lambda)
ob = cop->function.lambda.u.lambda->ob;
else
ob = cop->function.named.ob;
if ( !(user = ob->user) )
user = &default_wizlist_entry;
user->call_out_cost = eval_cost;
cop->v.type = T_INVALID;
free_call(cop);
}
tracedepth = 0;
sp = inter_sp+1;
while (call_list && call_list->delta <= 0) {
struct call *cop;
ph_int type;
int num_arg;
/*
* Move the first call_out out of the chain.
*/
cop = call_list;
call_list = cop->next;
current_call_out = cop;
/*
* A special case:
* If a lot of time has passed, so that current call out was missed,
* then it will have a negative delta. This negative delta implies
* that the next call out in the list has to be adjusted.
*/
if (cop->delta < 0 && call_list)
call_list->delta += cop->delta;
if (cop->command_giver && !(cop->command_giver->flags & O_DESTRUCTED))
{
command_giver = cop->command_giver;
if (command_giver->interactive)
trace_level = command_giver->interactive->trace_level;
} else {
struct object *ob;
if (cop->is_lambda)
ob = cop->function.lambda.u.lambda->ob;
else {
ob = cop->function.named.ob;
if (ob->flags & O_DESTRUCTED) {
free_call(cop);
continue;
}
}
while(ob->shadowing)
ob = ob->shadowing;
if (ob->flags & O_ENABLE_COMMANDS) {
command_giver = ob;
if (command_giver->interactive)
trace_level =
command_giver->interactive->trace_level;
} else {
command_giver = 0;
trace_level = 0;
}
}
if ((type = cop->v.type) == T_LVALUE) {
struct svalue *v;
int i;
struct object *ob;
v = cop->v.u.lvalue;
num_arg = i = cop->v.x.num_arg;
sp--;
do {
if (v->type == T_OBJECT &&
(ob = v->u.ob)->flags & O_DESTRUCTED)
{
sp++;
free_object(ob, "call_out");
sp->type = T_NUMBER;
sp->u.number = 0;
v++;
} else {
*++sp = *v++;
}
} while (--i);
xfree((char *)cop->v.u.lvalue);
inter_sp = sp;
sp -= num_arg - 1;
} else {
/* single argument */
struct object *ob;
if (type == T_OBJECT) {
ob = cop->v.u.ob;
if (ob->flags & O_DESTRUCTED) {
free_object(ob, "call_out");
sp->type = T_NUMBER;
sp->u.number = 0;
} else {
sp->type = T_OBJECT;
sp->u.ob = ob;
}
} else {
*sp = cop->v;
}
num_arg = 1;
inter_sp = sp;
}
if (cop->is_lambda) {
struct object *ob;
struct wiz_list *user;
ob = cop->function.lambda.u.lambda->ob;
current_object = ob;
if ( !(user = ob->user) )
user = &default_wizlist_entry;
if (user->last_call_out != current_time) {
user->last_call_out = current_time;
CLEAR_EVAL_COST;
} else {
assigned_eval_cost = eval_cost = user->call_out_cost;
}
call_lambda(&cop->function.lambda, num_arg);
user->call_out_cost = eval_cost;
free_closure(&cop->function.lambda);
free_svalue(sp);
} else {
struct object *ob;
if ((ob = cop->function.named.ob)->flags & O_DESTRUCTED) {
do {
pop_stack();
} while (--num_arg);
} else {
struct wiz_list *user;
current_object = ob;
if ( !(user = ob->user) )
user = &default_wizlist_entry;
if (user->last_call_out != current_time) {
user->last_call_out = current_time;
CLEAR_EVAL_COST;
} else {
assigned_eval_cost = eval_cost = user->call_out_cost;
}
sapply(cop->function.named.name, ob, num_arg);
user->call_out_cost = eval_cost;
}
free_string(cop->function.named.name);
free_object(cop->function.named.ob, "free_call");
}
free_used_call(cop);
}
inter_sp = sp - 1;
error_recovery_pointer = error_recovery_info.last;
}
/*
* 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.
*/
void remove_call_out(ob, fun)
struct object *ob;
struct svalue *fun;
{
struct call **copp, *cop;
int delay = 0;
char *fun_name;
if (fun->x.string_type == STRING_SHARED) {
fun_name = fun->u.string;
} else {
fun_name = make_shared_string(fun->u.string);
if (fun->x.string_type == STRING_MALLOC)
xfree(fun->u.string);
}
fun->type = T_NUMBER;
for (copp = &call_list; cop = *copp; copp = &cop->next) {
delay += cop->delta;
if (cop->function.named.ob == ob &&
cop->function.named.name == fun_name &&
!cop->is_lambda)
{
decrement_string_ref(fun_name);
if (cop->next)
cop->next->delta += cop->delta;
*copp = cop->next;
free_call(cop);
fun->u.number = delay;
return;
}
}
free_string(fun_name);
fun->u.number = -1;
}
void find_call_out(ob, fun)
struct object *ob;
struct svalue *fun;
{
struct call *cop;
int delay = 0;
char *fun_name;
if (fun->x.string_type == STRING_SHARED) {
fun_name = fun->u.string;
} else {
fun_name = make_shared_string(fun->u.string);
if (fun->x.string_type == STRING_MALLOC)
xfree(fun->u.string);
}
fun->type = T_NUMBER;
for (cop = call_list; cop; cop = cop->next) {
delay += cop->delta;
if (cop->function.named.ob == ob &&
cop->function.named.name == fun_name &&
!cop->is_lambda)
{
decrement_string_ref(fun_name);
fun->u.number = delay;
return;
}
}
free_string(fun_name);
fun->u.number = -1;
}
int print_call_out_usage(verbose)
int verbose;
{
int i;
struct call *cop;
for (i=0, cop = call_list; cop; cop = cop->next)
i++;
if (verbose) {
add_message("\nCall out information:\n");
add_message("---------------------\n");
add_message("Number of allocated call outs: %8d, %8d bytes\n",
num_call, num_call * sizeof (struct call));
add_message("Current length: %d\n", i);
} else {
add_message("call out:\t\t\t%8d %8d (current length %d)\n", num_call,
num_call * sizeof (struct call), i);
}
return num_call * sizeof (struct call);
}
#ifdef DEBUG
void count_extra_ref_from_call_outs()
{
struct call *cop;
for (cop = call_list; cop; cop = cop->next) {
if (cop->v.type == T_LVALUE)
{
count_extra_ref_in_vector(cop->v.u.lvalue, cop->v.x.num_arg);
} else {
count_extra_ref_in_vector(&cop->v, 1);
}
if (cop->is_lambda) {
count_extra_ref_in_vector(&cop->function.lambda, 1);
} else {
count_extra_ref_in_object(cop->function.named.ob);
}
if (cop->command_giver)
count_extra_ref_in_object(cop->command_giver);
}
}
#endif
void remove_stale_call_outs() {
struct call **copp, *cop;
for (copp = &call_list; cop = *copp; ) {
if (!cop->is_lambda) {
if (cop->function.named.ob->flags & O_DESTRUCTED) {
if (cop->next)
cop->next->delta += cop->delta;
*copp = cop->next;
free_call(cop);
continue;
}
}
copp = &cop->next;
}
}
#ifdef MALLOC_smalloc
void clear_ref_from_call_outs()
{
struct call *cop;
for (cop = call_list; cop; cop = cop->next) {
struct object *ob;
if (cop->v.type == T_LVALUE) {
struct svalue *v;
v = cop->v.u.lvalue;
clear_ref_in_vector(v, cop->v.x.num_arg);
} else {
clear_ref_in_vector(&cop->v, 1);
}
if (cop->is_lambda) {
clear_ref_in_vector(&cop->function.lambda, 1);
/* else: cop->function.named.ob isn't destructed */
}
if ((ob = cop->command_giver) && ob->flags & O_DESTRUCTED) {
ob->ref = 0;
ob->prog->ref = 0;
}
}
}
void count_ref_from_call_outs()
{
struct call *cop;
struct object *ob;
for (cop = call_list; cop; cop = cop->next) {
if (cop->v.type == T_LVALUE) {
struct svalue *v;
v = cop->v.u.lvalue;
count_ref_in_vector(v, cop->v.x.num_arg);
} else {
count_ref_in_vector(&cop->v, 1);
}
if (cop->is_lambda) {
count_ref_in_vector(&cop->function.lambda, 1);
} else {
/* destructed objects have been taken care of beforehand */
cop->function.named.ob->ref++;
count_ref_from_string(cop->function.named.name);
}
if (ob = cop->command_giver) {
if (ob->flags & O_DESTRUCTED) {
reference_destructed_object(ob);
cop->command_giver = 0;
} else {
ob->ref++;
}
}
}
}
#endif
/*
* Construct an array of all pending call_outs. Every item in the array
* consists of 4 items (but only if the object not is destructed):
* 0: The object.
* 1: The function (string).
* 2: The delay.
* 3: The argument.
* If there is more than one argument, the extra arguments are put
* in extra members of the array.
*/
struct vector *get_all_call_outs() {
int i, next_time;
struct call *cop;
struct vector *v;
for (i=0, cop = call_list; cop; cop = cop->next) {
if (!cop->is_lambda && cop->function.named.ob->flags & O_DESTRUCTED)
continue;
i++;
}
v = allocate_array(i);
next_time = 0;
/*
* Take for granted that all items in an array are initialized to
* number 0.
*/
for (i=0, cop = call_list; cop; cop = cop->next) {
struct vector *vv;
char *function_name;
next_time += cop->delta;
vv = allocate_array(cop->v.type == T_LVALUE ? 3 + cop->v.x.num_arg : 4);
if (cop->is_lambda) {
assign_svalue_no_free(&vv->item[1], &cop->function.lambda);
} else {
struct object *ob;
ob = cop->function.named.ob;
if (ob->flags & O_DESTRUCTED) {
free_vector(vv);
continue;
}
vv->item[0].type = T_OBJECT;
vv->item[0].u.ob = ob;
add_ref(ob, "get_all_call_outs");
vv->item[1].type = T_STRING;
vv->item[1].x.string_type = STRING_SHARED;
function_name = cop->function.named.name;
increment_string_ref(function_name);
vv->item[1].u.string = function_name;
}
vv->item[2].u.number = next_time;
if (cop->v.type == T_LVALUE) {
struct svalue *source, *dest;
int j;
source = cop->v.u.lvalue;
dest = &vv->item[3];
j = cop->v.x.num_arg;
do {
assign_svalue_no_free(dest++, source++);
} while (--j);
} else {
assign_svalue_no_free(&vv->item[3], &cop->v);
}
v->item[i].type = T_POINTER;
v->item[i].u.vec = vv; /* Ref count is already 1 */
i++;
}
return v;
}