To run the necessary rulebooks to carry out an activity.


§1. The Activities Stack. Activities are more like nested function calls than independent processes; they finish in reverse order of starting, and are placed on a stack. This needs only very limited size in practice: 20 might seem a bit low, but making it much higher simply means that oddball bugs in the user's code — where activities recursively cause themselves ad infinitum — will be caught less efficiently.

Constant MAX_NESTED_ACTIVITIES = 20;
Global activities_sp = 0;
Array activities_stack --> MAX_NESTED_ACTIVITIES;
Array activity_parameters_stack --> MAX_NESTED_ACTIVITIES;

§2. The Activity_flags byte array, indexed by activity number, is a bitmap:

Constant FUTURE_ACTION_ACTFLAG = 1;
Constant HIDDEN_IN_RULES_ACTFLAG = 2;

§3. These variables are not needed by Basic Inform, and are unused in that case, but are included in BasicInformKit anyway in order that the activities code below can compile in both Basic and regular Inform projects.

Global player;
Global action;
Global action_to_be; (if the current command line being parsed were accepted)

§4. Rule Debugging Inhibition. The output from RULES or RULES ALL becomes totally illegible if it is applied even to the activities printing names of objects, so this is inhibited when any such activity is running. FixInhibitFlag is called each time the stack changes and ensures that inhibit_flag has exactly this meaning.

Global inhibit_flag = 0;
Global saved_debug_rules = 0;

[ FixInhibitFlag n inhibit_rule_debugging;
    for (n=0:n<activities_sp:n++)
        if ((Activity_flags->(activities_stack-->n)) & HIDDEN_IN_RULES_ACTFLAG) {
            inhibit_rule_debugging = true;
            break;
        }
    if ((inhibit_flag == false) && (inhibit_rule_debugging)) {
        saved_debug_rules = debug_rules;
        debug_rules = 0;
    }
    if ((inhibit_flag) && (inhibit_rule_debugging == false)) {
        debug_rules = saved_debug_rules;
    }
    inhibit_flag = inhibit_rule_debugging;
];

§5. Testing Activities. The following tests whether a given activity A is currently running whose parameter-object matches description desc, where as usual the description is represented by a routine testing membership, and where zero desc means that any parameter is valid. Alternatively, we can require a specific parameter value of val.

[ TestActivity A desc val i;
    for (i=0:i<activities_sp:i++)
        if (activities_stack-->i == A) {
            if (desc) {
                if ((desc)(activity_parameters_stack-->i)) rtrue;
            } else if (val) {
                if (val == activity_parameters_stack-->i) rtrue;
            } else rtrue;
        }
    rfalse;
];

§6. Emptiness. An activity is defined by its three rulebooks: it is empty if they are all empty.

[ ActivityEmpty A x;
    x = Activity_before_rulebooks-->A;
    if (rulebooks_array-->x ~= EMPTY_RULEBOOK) rfalse;
    x = Activity_for_rulebooks-->A;
    if (rulebooks_array-->x ~= EMPTY_RULEBOOK) rfalse;
    x = Activity_after_rulebooks-->A;
    if (rulebooks_array-->x ~= EMPTY_RULEBOOK) rfalse;
    rtrue;
];

[ RulebookEmpty rb;
    if (rulebooks_array-->rb ~= EMPTY_RULEBOOK) rfalse;
    rtrue;
];

§7. Process Activity Rulebook. This is really much like processing any rulebook, except that self is temporarily set to the parameter, and is preserved by the process.

Note that when an activity based on the conjectural "future action" is being run — in a few parser-related cases, that is — the identity of this action is put temporarily into action, and the current value saved while this takes place. That allows rules in the activity rulebooks to have preambles based on the current action, and yet be tested against what is not yet the current action.

[ ProcessActivityRulebook rulebook parameter  rv;
    @push self;
    if (parameter) self = parameter;
    rv = FollowRulebook(rulebook, parameter, true);
    @pull self;
    if (rv) rtrue;
    rfalse;
];

[ ProcessActivityRulebookATB rulebook parameter  rv;
    @push action; action = action_to_be;
    @push self;
    if (parameter) self = parameter;
    rv = FollowRulebook(rulebook, parameter, true);
    @pull self;
    @pull action;
    if (rv) rtrue;
    rfalse;
];

§8. Carrying Out Activities. This is a three-stage process; most activities are run by calling the following simple routine, but some are run by calling the three subroutines independently.

[ CarryOutActivity A o rv;
    BeginActivity(A, o);
    rv = ForActivity(A, o);
    EndActivity(A, o);
    return rv;
];

§9. Begin.

[ BeginActivity A o;
    if (activities_sp == MAX_NESTED_ACTIVITIES)
        return IssueRTP("TooManyActivities",
            "Too many activities are going on at once.", BasicInformKitRTPs);
    activity_parameters_stack-->activities_sp = o;
    activities_stack-->(activities_sp++) = A;
    FixInhibitFlag();
    MStack_CreateAVVars(A);
    if ((Activity_flags->A) & FUTURE_ACTION_ACTFLAG)
        return ProcessActivityRulebookATB(Activity_before_rulebooks-->A, o);
    return ProcessActivityRulebook(Activity_before_rulebooks-->A, o);
];

§10. For.

[ ForActivity A o;
    if ((Activity_flags->A) & FUTURE_ACTION_ACTFLAG)
        return ProcessActivityRulebookATB(Activity_for_rulebooks-->A, o);
    return ProcessActivityRulebook(Activity_for_rulebooks-->A, o);
];

§11. End.

[ EndActivity A o rv x;
    if ((activities_sp > 0) && (activities_stack-->(activities_sp-1) == A)) {
        if ((Activity_flags->A) & FUTURE_ACTION_ACTFLAG)
            rv = ProcessActivityRulebookATB(Activity_after_rulebooks-->A, o);
        else
            rv = ProcessActivityRulebook(Activity_after_rulebooks-->A, o);
        activities_sp--; FixInhibitFlag();
        MStack_DestroyAVVars(A);
        return rv;
    }
    return IssueRTP("EndedNonRunningActivity",
        "Tried to end an activity which wasn't going on.", BasicInformKitRTPs);
];

§12. Abandon. For (very) rare cases where an activity must be abandoned midway; such an activity must be being run by calling the three stages individually, and EndActivity must not have been called yet.

[ AbandonActivity A o;
    if ((activities_sp > 0) && (activities_stack-->(activities_sp-1) == A)) {
        activities_sp--; FixInhibitFlag();
        MStack_DestroyAVVars(A);
        return;
    }
    return IssueRTP("AbandonedNonRunningActivity",
        "Tried to abandon an activity which wasn't going on.", BasicInformKitRTPs);
];