question

Tom S4 avatar image
1 Like"
Tom S4 asked Jordan Johnson commented

Custom Acquire Resource activity (ProcessFlow Module SDK)

I'm looking to create a custom activity (using the ProcessFlow module SDK) that can acquire multiple types of Resources simultaneously. No Resource should be acquired until all can be acquired, and ideally requests for multiple Resources will track on all of the requested resources Request Content and Request Staytime (even if some of the resources are available).

I can easily check if a resource is available, so that solves one problem:

getstat(theresource, "ResourceAvailable", STAT_CURRENT)

However, I can't figure out how the methods in the Acquire class works. This is my understanding so far:

  1. Token Enters
  2. Acquire::onBlockingStart is called.
    • I am assuming this is where most of the logic is. My best guess is that this does something along the lines of grab the reference to the resource and call Resource::acquireResource, but I can't figure out what half of the parameters for this function are.
  3. If max wait timer is triggered, Acquire::releaseToken
  4. Acquire::onBlockingFinish (token is automatically released)


So, I am looking to understand:

  1. The order of operations starting with when a token enters an Acquire block
  2. How I can acquire a resource (the parameters to Resource::acquireResource are challenging to figure out without seeing under the hood)
  3. If it would be possible to have a request, that can be fulfilled, not be fulfilled until something else allows it to (might have to create a custom Resource block as well?)

Thank you

FlexSim 23.0.1
processflowmodule sdkshared resources
· 1
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Tom S4 avatar image Tom S4 commented ·

To give credit to @Jordan Johnson, I've been using a modified version of this approach (except with resources instead of lists) for almost two years now: https://answers.flexsim.com/questions/98195/simultaneous-all-or-nothing-list-pulls.html

To say it has saved hours of my time would be an understatement. I would, however, like to potentially gain more customizability and more accurate statistics tracking by turning this into a single activity.

0 Likes 0 ·

1 Answer

Jordan Johnson avatar image
0 Likes"
Jordan Johnson answered Jordan Johnson commented

Your question is actually quite complicated, but mostly because the Resource is complicated. Here are some of the complexities that the Resource handles:

  • Sometimes the Resource uses a list under the hood. In fact, this is the default case, unless the resource is purely numeric. If you reference an object or group, then the Resource pushes and pulls those values using an internal list. The resource just provides a simpler interface than the full-blown Push To List or Pull From List UIs.
  • By design, the "amount available" for a resource can change during the model run. The concept is that a numeric resource might represent something like "power available" and might change during the day. I don't know if I've seen anyone use it that way, but the code is there to handle it. It makes calculating the "amount available" really difficult.
  • As with most shared assets, instances may be present. A numeric resource can exists for each attached object. So that's another set of if/then statements.

So I don't think it's really possible to communicate how the Resource works internally as an answers post.

However, I can offer guidance on how to design your shared asset:

  • I would use a list under the hood and create a simplified UI, like the resource does. You can use the List's C++ API to internally manage pushing, pulling, and checking (pulling with a "don't pull" flag).
  • If possible, I'd just link your shared asset to Global Lists in the toolbox. This means you don't have to worry about instances of your process flow. It also means it's easy for users to add/remove lists and use them with your activity. This would also mean you don't have to worry about creating internal lists and using those.
  • Essentially, you can translate the "all-or-nothing" approach directly into code
    • Use the List C++API to do a test pull on all the target lists
    • If any fail, add a listener to that list and wait for that list to get a push
    • If all succeed, actually pull the resources
  • If you are pulling from multiple lists, I'm not really sure how you'd frame statistics. For example, is Content for your Shared asset the number of available resources across all lists? If so, it seems like you'd want to listen to the stats of all involved lists and update the totals for your own stats. Just something to think about.

By designing your own, you can make the shared asset as complex or as simple as you need, and you won't have any impenetrable black boxes to worry about.

· 2
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Tom S4 avatar image Tom S4 commented ·
Thanks for the detailed response. I think I can make this approach work. I have a follow-up - could you provide a quick example of creating and attaching an event listener to a resource's pull/push event? And if I were to create my shared asset based on the Resource class, would that listener need to be on the internal list or could it be on the Resource itself?


Thanks again.

0 Likes 0 ·
Jordan Johnson avatar image Jordan Johnson ♦♦ Tom S4 commented ·

Listening to events is a little tricky. For an example of this process, you can look at the ReinforcementLearning object's code. Look at this node in the library:

MAIN:/project/library/ReinforcementLearning>behaviour/includebody

Look specifically at the ReinforcementLearning::addDecisionListener() method. This is the best method to use.

For a List, you should probably listen to the OnPush event. If you are listening to a Global List, then the ReinforcementLearning code will work fine. If you are listening to a Process Flow List or Resource, you'll need to add an argument to the assertEvent(), which should be the instance object (ignored for General Flows, the attached object for object flows).

Each event has parameters associated with it. To get those parameters, use C++ code like this inside your ::execute() method on your FlexSimEvent class:

CallPoint* callPoint = getListenerCallPoint();
Variant p = callPoint->getParam(paramNumber);
1 Like 1 ·