//: Routines can be put in a 'waiting' state, from which it will be ready to //: run again when a specific memory location changes its value. This is mu's //: basic technique for orchestrating the order in which different routines //: operate. :(scenario wait_for_location) recipe f1 [ 1:number <- copy 0 start-running f2:recipe wait-for-location 1:number # now wait for f2 to run and modify location 1 before using its value 2:number <- copy 1:number ] recipe f2 [ 1:number <- copy 34 ] # if we got the synchronization wrong we'd be storing 0 in location 2 +mem: storing 34 in location 2 //: define the new state that all routines can be in :(before "End routine States") WAITING, :(before "End routine Fields") // only if state == WAITING long long int waiting_on_location; int old_value_of_waiting_location; :(before "End routine Constructor") waiting_on_location = old_value_of_waiting_location = 0; //: primitive recipe to put routines in that state :(before "End Primitive Recipe Declarations") WAIT_FOR_LOCATION, :(before "End Primitive Recipe Numbers") Recipe_ordinal["wait-for-location"] = WAIT_FOR_LOCATION; :(before "End Primitive Recipe Implementations") case WAIT_FOR_LOCATION: { reagent loc = canonize(current_instruction().ingredients.at(0)); Current_routine->state = WAITING; Current_routine->waiting_on_location = loc.value; Current_routine->old_value_of_waiting_location = Memory[loc.value]; trace(Primitive_recipe_depth, "run") << "waiting for location " << loc.value << " to change from " << no_scientific(Memory[loc.value]) << end(); break; } //: scheduler tweak to get routines out of that state :(before "End Scheduler State Transitions") for (long long int i = 0; i < SIZE(Routines); ++i) { if (Routines.at(i)->state != WAITING) continue; if (Routines.at(i)->waiting_on_location && Memory[Routines.at(i)->waiting_on_location] != Routines.at(i)->old_value_of_waiting_location) { trace("schedule") << "waking up routine\n" << end(); Routines.at(i)->state = RUNNING; Routines.at(i)->waiting_on_location = Routines.at(i)->old_value_of_waiting_location = 0; } } //: also allow waiting on a routine to stop running :(scenario wait_for_routine) recipe f1 [ 1:number <- copy 0 12:number/routine <- start-running f2:recipe wait-for-routine 12:number/routine # now wait for f2 to run and modify location 1 before using its value 3:number <- copy 1:number ] recipe f2 [ 1:number <- copy 34 ] +schedule: f1 +run: waiting for routine 2 +schedule: f2 +schedule: waking up routine 1 +schedule: f1 # if we got the synchronization wrong we'd be storing 0 in location 3 +mem: storing 34 in location 3 :(before "End routine Fields") // only if state == WAITING long long int waiting_on_routine; :(before "End routine Constructor") waiting_on_routine = 0; :(before "End Primitive Recipe Declarations") WAIT_FOR_ROUTINE, :(before "End Primitive Recipe Numbers") Recipe_ordinal["wait-for-routine"] = WAIT_FOR_ROUTINE; :(before "End Primitive Recipe Implementations") case WAIT_FOR_ROUTINE: { if (SIZE(ingredients) != 1) { raise << maybe(current_recipe_name()) << "'wait-for-routine' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end(); break; } if (!scalar(ingredients.at(0))) { raise << maybe(current_recipe_name()) << "first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end(); break; } if (ingredients.at(0).at(0) == Current_routine->id) { raise << maybe(current_recipe_name()) << "routine can't wait for itself! " << current_instruction().to_string() << '\n' << end(); break; } Current_routine->state = WAITING; Current_routine->waiting_on_routine = ingredients.at(0).at(0); trace(Primitive_recipe_depth, "run") << "waiting for routine " << ingredients.at(0).at(0) << end(); break; } :(before "End Scheduler State Transitions") // Wake up any routines waiting for other routines to go to sleep. // Important: this must come after the scheduler loop above giving routines // waiting for locations to change a chance to wake up. for (long long int i = 0; i < SIZE(Routines); ++i) { if (Routines.at(i)->state != WAITING) continue; if (!Routines.at(i)->waiting_on_routine) continue; long long int id = Routines.at(i)->waiting_on_routine; assert(id != Routines.at(i)->id); // routine can't wait on itself for (long long int j = 0; j < SIZE(Routines); ++j) { if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) { trace("schedule") << "waking up routine " << Routines.at(i)->id << end(); Routines.at(i)->state = RUNNING; Routines.at(i)->waiting_on_routine = 0; } } } :(before "End Primitive Recipe Declarations") SWITCH, :(before "End Primitive Recipe Numbers") Recipe_ordinal["switch"] = SWITCH; :(before "End Primitive Recipe Implementations") case SWITCH: { long long int id = some_other_running_routine(); if (id) { assert(id != Current_routine->id); Current_routine->state = WAITING; Current_routine->waiting_on_routine = id; } break; } :(code) long long int some_other_running_routine() { for (long long int i = 0; i < SIZE(Routines); ++i) { if (i == Current_routine_index) continue; assert(Routines.at(i) != Current_routine); assert(Routines.at(i)->id != Current_routine->id); if (Routines.at(i)->state == RUNNING) return Routines.at(i)->id; } return 0; }