Thom DeCarlo
Thom DeCarlo

Reputation: 169

API function to add an Action to an Event or Schedule?

I need to add an Action to a Schedule object that is being created through the API. There are documented interfaces to set almost all the options except the Action. How are Actions attached to these Objects?

When I attempt to programmatically add a new event, read from a separate configuration file, to a Schedule object I get errors stating that the Schedule has already been initialized and that I must construct a new object and add its configuration manually. I can do most of that using the available Schedule API. I can set up everything about the Schedule except the Action code.

The Schedule is used in a Process Model. Looking at the model in the Java editor, I see the code I'm trying to replicate via the API in a function that looks like this:

  @Override
  @AnyLogicInternalCodegenAPI
  public void executeActionOf( EventTimeout _e ) {
    if ( _e == _fuelDeliverySchedule_Action_xjal ) {
      Schedule<Integer> self = this.fuelDeliverySchedule;
      Integer value = fuelDeliverySchedule.getValue();

logger.info("{} received {} pounds of fuel", this.getName(), this.fuelDeliverySchedule.getValue());

this.fuelAvailablePounds += fuelDeliverySchedule.getValue();

;
      _fuelDeliverySchedule_Action_xjal.restartTo( fuelDeliverySchedule.getTimeOfNextValue() );
      return;
    }
    super.executeActionOf( _e );
  }

Maybe I can use something like this to create my own action function, but I'm not sure how to make the Scheduled event use it.

Thanks, Thom

Upvotes: 2

Views: 1726

Answers (1)

Stuart Rossiter
Stuart Rossiter

Reputation: 2517

[Edited (expanded/rewrote) 03.11.2014 after more user detail on the context.]

You clarified the context with

When I attempt to programatically add "a thing that happens", read from a separate configuration file, to a Schedule object I get errors stating that the Schedule has already been initialized and that I must construct a new object and add its configuration manually. I can do most of that using the available Schedule API. I can set up everything about the Schedule except the Action code.

(You might want to edit that into the question... In general, it's always good to explain the context for why you're trying to do the thing.)

I think I understand now. I presume that your config file contains scheduling details and, when you say you were trying to "add a thing that happens" (which errored), you meant that you were trying to change the scheduling 'pattern' in the Schedule. So your problem is that, since you couldn't adjust a pre-existing schedule, you had to instantiate (create) your own programmatically, but the Schedule API doesn't allow you to set the action code (as seen on the GUI schedule element).

This is a fairly involved solution so bear with me. I give a brief 'tl;dr' summary before diving into the detail.

Summary

You can't programmatically code an AnyLogic action (for any element) because that would amount to dynamically creating a Java class. Solving your problem requires recognising that the schedule GUI element creates both a Schedule instance and a timeout event (EventTimeout) instance to trigger the action. You can therefore create these two elements explicitly yourself (the former dynamically). The trick is to reset the timeout event when you replace the Schedule instance (to trigger at the next 'flip' point of the new Schedule).

[Actually, from your wording, I suspect that the action is always the same but, for generality, I show how you could handle it if your config file details might want to change the nature of the action as well as those of the scheduling pattern.]

Detail

The issue is that the GUI element (confusingly) isn't just a Schedule instance in terms of the code it generates. There is one (with the same name as that of the GUI element), which just contains the schedule 'pattern' and, as in the API, has methods to determine when the next on/off period (for an on/off schedule) occurs. (So it is kind of fancy calendar functionality.) But AnyLogic also generates a timeout event to actually perform the action; if you look further in the code generated, you'll see stuff similar to the below (assuming your GUI schedule is called fuelSchedule, with Java comments added by me):

  // Definition of the timeout event
  @AnyLogicInternalCodegenAPI
  public EventTimeout _fuelSchedule_Action_xjal = new EventTimeout(this);

  // First occurrence time of the event as the next schedule on/off change
  // time
  @Override
  @AnyLogicInternalCodegenAPI
  public double getFirstOccurrenceTime( EventTimeout _e ) {

    if ( _e == _fuelSchedule_Action_xjal ) return fuelSchedule.getTimeOfValue() == time() ? time() : fuelSchedule.getTimeOfNextValue();
    return super.getFirstOccurrenceTime( _e );
  }

  // After your user action code, the event is rescheduled for the next
  // schedule on/off change time
   _fuelSchedule_Action_xjal.restartTo( fuelSchedule.getTimeOfNextValue() );

i.e., this creates an event which triggers each time the schedule 'flips', and performs the action specified in the GUI schedule element.

So there is no action to change on the Schedule instance; it's actually related to the EventTimeout instance. However, you can't programmatically change it there (or create a new one dynamically) for the same reason that you can't change the action of any AnyLogic element: this would effectively be programmatically creating a Java class definition, which isn't possible without very specialised Java code. (You can create Java source code in a string and dynamically run a Java compiler on it to generate a class. However, this is very 'advanced' Java, has lots of potential pitfalls, and I would definitely not recommend going that route. You would also have to be creating source for a user subclass of EventTimeout, since you don't know the correct source code for AnyLogic's proprietary EventTimeout class, and this might change per release in any case.)

But you shouldn't need to: there should be a strict set of possible actions that your config file can contain. (They can't be arbitrary Java code snippets, since they have to 'fit in' with the simulation.) So you can do what you want by programmatically creating the Schedule but with a GUI-created timeout event that you adjust accordingly(assuming an off/on schedule here and that there is only one schedule active at once; obviously tweak this skeleton to your needs and I haven't completely tested this in AnyLogic):

1. Have an AnyLogic variable activeAction which specifies the current active action. (I take this as an int here for simplicity, but it's better to use a Java enum which is the same as an AnyLogic 7 Option List, and can just be created in raw Java in AnyLogic 6.)

2. Create a variable in the GUI, say called fuelSchedule, of type Schedule but with initial value null. Create a separate timeout event, say called fuelScheduleTrigger, in User Control mode, with action as:

// Perform the appropriate action (dependent on activeAction)
doAppropriateScheduleAction();
// Set the event to retrigger at the next schedule on/off switch time
fuelScheduleTrigger.restartTo(fuelSchedule.getTimeOfNextValue());

(Being in User Control mode, this event isn't yet triggered to initially fire, which is what we want.)

3. Code a set of functions for each of the different action alternatives; let's say there are only 2 (fuelAction1 and fuelAction2) here as an example. Code doAppropriateScheduleAction as:

if (activeAction == 1) {
    fuelAction1();
}
else if (activeAction == 2) {
    fuelAction2();
}

4. In your code which reads the config file and gets updated schedule info. (presumably run from a cyclic timeout event or similar), have this replace fuelSchedule with a new instance with the revised schedule pattern (as you've been doing), set activeAction appropriately, and then reset the timeout event to the new fuelSchedule.getTimeOfValue() time:

[set up fuelSchedule and activeAction]
// Reset schedule action to match revised schedule
fuelScheduleTrigger.restartTo(fuelSchedule.getTimeOfNextValue());

I think this works OK in the edge case when the new Schedule had its next 'flip' at the time you set it up. (If you restart an event to the current time, I think it schedules an event OK at the current time which will occur next if there are no other events also scheduled for the current time; actually, it will definitely occur next if you are using a LIFO simultaneous-time-scheduling regime---see my blog post.)

Alternative & AnyLogic Enhancement

An alternative is to create a 'full' schedule in the GUI with action as earlier. Your config file reading code can replace the underlying Schedule instance and then reset the internal AnyLogic-generated timeout event. However, this is less preferable because you are relying on an internally-named AnyLogic event (which might also change in future AnyLogic releases, breaking your code).

AnyLogic could help this situation by adding a method to the Schedule API that gets the related timeout event; e.g., getActionTriggeringEventTimeout(). Then you would be able to 'properly' restart it and the Schedule API would make much clearer that the Schedule was always associated with an EventTimeout that did the triggering for the action.

Of course, AnyLogic could also go further by changing Schedule to allow scheduling details to be changed dynamically (and internally handling the required updates to the timeout event if it continued to be designed like that), but that's a lot more work and there may be deeper technical reasons why they wanted the schedule pattern to be fixed once the Schedule is initialised.

Any AnyLogic support staff reading?

Upvotes: 2

Related Questions