The verb cache

	This object acts as a delegate for $root, $located, $user, and
	$has_verbs. It keeps track of the verb templates provided by
	all objects in each live container in the database, where a
	"live container" is a connected user or a room containing at
	least one connected user.

	This object also keeps track of remote verb templates, a fairly easy
	job.

parent root
object verb_cache

var root name 'verb_cache
var verb_cache remote_verb_templates #[]
var verb_cache container_verbs #[]
var verb_cache container_objects #[]
var verb_cache object_containers #[]
var verb_cache room_users #[]
var verb_cache users []

eval
    .initialize();
.

method object_defined_remote_verb
    arg verb;
    var obj;

    if (caller() != $has_verbs)
        throw(~perm, "Caller is not $has_verbs.");
    obj = sender();

    // Add verb to our remote_verb_templates dictionary.
    remote_verb_templates = dict_add_elem(remote_verb_templates, verb, obj);
.

method object_undefined_global_verb
    arg verb;
    var obj;

    if (caller() != $has_verbs)
        throw(~perm, "Caller is not $has_verbs.");
    obj = sender();

    // Remove verb from our remote_verb_templates dictionary.
    remote_verb_templates = dict_del_elem(remote_verb_templates, verb, obj);
.

method remote_verb_templates
    return dict_keys(remote_verb_templates);
.

method object_defined_verb
    arg verb;
    var obj, container, verbs;

    if (caller() != $has_verbs)
        throw(~perm, "Caller is not $has_verbs.");
    obj = sender();

    // If neither the object nor its descendents are in anything's environment,
    // don't do anything.
    if (!dict_contains(object_containers, obj))
        return;

    // Update container_verbs entry for each container the object (or a
    // descendent) is in.  The second line of this loop is a kludge to permit
    // the third line to mutate rather than copy container_verbs[container].
    for container in (dict_keys(object_containers[obj])) {
        verbs = container_verbs[container];
        container_verbs = dict_add(container_verbs, container, 0);
        verbs = dict_add_elem(verbs, verb, obj);
        container_verbs = dict_add(container_verbs, container, verbs);
    }
.

method object_undefined_verb
    arg verb;
    var obj, container, verbs;

    if (caller() != $has_verbs)
        throw(~perm, "Caller is not $has_verbs.");
    obj = sender();

    // If neither the object nor its descendents are in anything's environment,
    // don't do anything.
    if (!dict_contains(object_containers, obj))
        return;

    // Update container_verbs entry for each container the object (or a
    // descendent) is in.  The second line of this loop is a kludge to permit
    // the third line to mutate rather than copy container_verbs[container].
    for container in (dict_keys(object_containers[obj])) {
        verbs = container_verbs[container];
        container_verbs = dict_add(container_verbs, container, 0);
        verbs = dict_del_elem(verbs, verb, obj);
        container_verbs = dict_add(container_verbs, container, verbs);
    }
.

method object_entered_container
    arg container;
    var obj;

    if (caller() != $located && caller() != $root)
        throw(~perm, "Caller is not $located or $root.");
    obj = sender();

    // If the container isn't in anything's environment, don't do anything.
    if (!dict_contains(container_objects[container]))
        return;

    // Add obj to our records for container.
    .add_object_to_container(container, obj);
.

method object_left_container
    arg container;
    var obj;

    if (caller() != $located && caller() != $root)
        throw(~perm, "Caller is not $located or $root.");
    obj = sender();

    // If the container isn't in anything's environment, don't do anything.
    if (!dict_contains(container_objects[container]))
        return;

    .remove_object_from_container(container, obj);
.

method user_entered_room
    arg room;
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();

    // Update room_users[room].
    room_users = dict_add_elem(room_users, room, user);
.

method user_left_room
    arg room;
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();

    // Update room_users[room].
    room_users = dict_del_elem(room_users, room, user);

    // If there aren't any users in the room, stop keeping track of that room
    // as a container.
    if (!dict_contains(room_users, room))
        .forget_container(room);
.

method user_connected
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();

    // Add the user to the user list, thus allowing things to ask for its verb
    // list.
    users = setadd(users, user);
.

method user_disconnected
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();

    // Remove the user from the users list.
    users = setremove(users, user);

    // Forget about the user as a container.
    .forget_container(user);
.

method container_verbs
    arg container;

    if (!(container in users) && !dict_contains(room_users, container))
        throw(~perm, "Can't ask for verbs for an inactive container.");

    // If we're not keeping track of container, start doing so.
    .keep_track_of_container(container);

    // Return the verbs for container.
    return dict_keys(container_verbs[container]);
.

method gc
    var user;

    if (!.is_owned_by(sender()))
        throw(~perm, "Sender is not an owner.");

    // Wipe all records of containers.
    container_verbs = #[];
    container_objects = #[];
    object_containers = #[];

    // Get users list from $user_db.
    users = $user_db.connected_users();

    // Rebuild room_users.
    room_users = #[];
    for user in (users)
        room_users = dict_add_elem(room_users, user.location(), user);
.

method keep_track_of_container
    arg container;
    var obj;

    if (caller() != definer())
        throw(~perm, "Invalid access to private method.");

    // Perform no action of we were already keeping track of this container.
    if (dict_contains(container_objects, room))
        return;

    // Create blank entries for container in container_objects and
    // container_verbs.
    container_objects = dict_add(container_objects, container, #[]);
    container_verbs = dict_add(container_verbs, container, #[]);

    // For each object in the container, and the container itself, add the
    // object to our records for the container.
    for obj in ([container, @contiainer.contents()])
        .add_object_to_container(container, obj);
.

method forget_container
    arg container;
    var obj, containers;

    if (caller() != definer())
        throw(~perm, "Invalid access to private method.");

    // Perform no action if we weren't keeping track of this room.
    if (!dict_contains(container_objects, room))
        return;

    // Remove container from object_containers[obj] for each object in
    // container_objects[container].  The dict_add(..., ..., 0) is a kludge to
    // free the reference count on the dictionary's copy of containers.  If
    // object_containers[obj] becomes empty after removing the association for
    // container, delete the association for obj from object_containers.
    for obj in (dict_keys(container_objects[room])) {
        containers = object_containers[obj];
        object_containers = dict_add(object_containers, obj, 0);
        containers = dict_del_elem(object_containers, container, obj);
        if (containers)
            object_containers = dict_add(object_containers, obj, containers);
        else
            object_containers = dict_del(object_containers, obj);
    }

    // Remove the associations for container from container_objects and
    // container_verbs.
    container_objects = dict_del(container_objects, container);
    container_verbs = dict_del(container_verbs, container);
.

method add_object_to_container
    arg container, obj;
    var objects, ancestor, templates, verbs, verb, containers;

    if (caller() != definer())
        throw(~perm, "Invalid access to private method.");

    // Update container_objects[container] to include each of obj's ancestors.
    // This will add obj to the container_objects[container][ancestor]
    // association for each ancestor.  If this creates a new association, add
    // the verbs for that ancestor to container_verbs[container].  The
    // dict_add(..., ..., 0) is a kludge to free the reference count from the
    // dictionary.  Also update object_containers[ancestor] for each ancestor.
    objects = container_objects[container];
    container_objects = dict_add(container_objects, container, 0);
    for ancestor in (obj.ancestors()) {
        if (!dict_contains(objects, ancestor)) {
            // Add the verbs for this ancestor to container_verbs[container].
            // Again, a kluge to free the reference count from the dictionary
            // before modifying the entry.
            templates = (| ancestor.verb_templates() |) || [];
            for verb in (templates) {
                verbs = container_verbs[container];
                container_verbs = dict_add(container_verbs, container, 0);
                verbs = dict_add_elem(verbs, verb, ancestor);
                container_verbs = dict_add(container_verbs, container, verbs);
            }
        }

        // Update container_objects[container].
        objects = dict_add_elem(objects, ancestor, obj);

        // Update object_containers[ancestor].  Usual kluge.  object_containers
        // may have no association for ancestor yet, so be prepared to make
        // one.
        containers = (| object_containers[ancestor] |) || #[];
        object_containers = dict_add(object_containers, ancestor, 0);
        containers = dict_add_elem(containers, container, obj);
        object_containers = dict_add(object_containers, ancestor, containers);
    }
    container_ojects = dict_add(container_objects, container, objects);
.

method remove_object_from_container
    arg container, obj;
    var objects, ancestor, verbs, verb;

    if (caller() != definer())
        throw(~perm, "Invalid access to private method.");

    // Update container_objects[container] to include each of obj's ancestors.
    // This will add obj to the container_objects[container][ancestor]
    // association for each ancestor.  If this creates a new association, add
    // the verbs for that ancestor to container_verbs[container].  The
    // dict_add(..., ..., 0) is a kludge to free the reference count from the
    // dictionary.
    objects = container_objects[container];
    container_objects = dict_add(container_objects, container, 0);
    for ancestor in (obj.ancestors()) {
        if (!dict_contains(objects, ancestor)) {
            // Add the verbs for this ancestor to container_verbs[container].
            // Again, a kluge to free the reference count from the dictionary
            // before modifying the entry.
            for verb in (ancestor.verb_templates()) {
                verbs = container_verbs[container];
                container_verbs = dict_add(container_verbs, container, 0);
                verbs = dict_del_elem(verbs, verb, ancestor);
                container_verbs = dict_add(container_verbs, container, verbs);
            }
        }

        // Update container_objects[container].
        objects = dict_del_elem(objects, ancestor, obj);

        // Update object_containers[ancestor].  Usual kluge.  If
        // object_containers[ancestor] becomes empty, remove the association
        // from object_containers.
        containers = object_containers[ancestor];
        object_containers = dict_add(object_containers, ancestor, 0);
        containers = dict_del_elem(containers, container, obj);
        if (containers)
            object_containers = dict_add(object_containers, ancestor, containers);
        else
            object_containers = dict_del(object_containers, ancestor);
    }
    container_ojects = dict_add(container_objects, container, objects);
.