trevordevore / levure

Application development framework for LiveCode
MIT License
32 stars 14 forks source link

Send `loadBehavior` to UI behaviors #153

Closed macMikey closed 4 years ago

macMikey commented 4 years ago

Would it be beneficial to send loadBehavior to ui behavior stacks, too? Example - any project that uses theming from tmc2 has behaviors assigned to every object. That chain is typically two or three deep. The stack also has a chained master behavior assigned to it.

trevordevore commented 4 years ago

I don't think that it is necessary to do so. Originally SOS stacks didn't allow you to define the parent behavior so there was no way to have a parent behavior assigned to an SOS stack unless you assigned it at run-time. Now that the engine support the with behavior syntax for SOS stacks this isn't needed.

LoadBehavior was added prior to with behavior being available and was a workaround of sorts. It may still have some value but I haven't set aside time to think through it.

The underlying issue that still needs to be addressed is that when a stack loads that has a behavior assigned to it (or if any controls in the stack have behaviors assigned to them), the behavior will not be associated with the stack (or control) if the behavior object is not loaded in memory.

Example scenario where the behavior will not be associated with the stack:

  1. Stack A is a behavior script.
  2. Stack B has Stack A assigned to it as a behavior.
  3. You load Stack B into memory and Stack A is not assigned to the stackFiles property of Stack B (or any other stack already in memory).
  4. You load Stack A into memory.

When Stack B opens Stack A is not available and cannot be found by the engine. Thus the behavior assignment fails and your code doesn't work as expected.

Unfortunately the engine doesn't send any notifications when a behavior fails to load. It just fails silently.

As I write this, it occurs to me that LoadBehavior can probably be done away with if all stacks in the behaviors folder are added to the stackFiles property of the app stack. All UI stacks are added as stackFiles which means you can always reference the stack by name and the engine will know where to find it. The same thing could be done with behaviors so the engine will always know where to find the behavior stack when needed.

macMikey commented 4 years ago

The stackfiles idea is fine until the stack ceases to be, then instead of the dreaded silent fail you get a bizarre message from the debugger

Screen Shot 2020-06-27 at 12 24 15

Maybe this is the same fail that you were seeing. In this case it occurred because there was a behavior in the stackfiles that had been removed from the project, and there was still a reference to it in a behavior chain.

In the short term, I don't think dropping the existing syntax is a good idea. Navigator still generates behaviors the old way and then outputs a list of script lines to be added to a stack to ensure that the new SOS's get their behaviors assigned. That said, I'm going to issue an issue with GC to update it. Unfortunately, Levure is going to have to consider its place as the de facto LC framework, and therefore be considerate of the unwashed masses coming for it, as itty bitty as they may be.

macMikey commented 4 years ago

although, you know what? i'm also the only person who even noticed this, so maybe i'm thinking about something that doesn't even matter, and the plan should be to tell everyone to use the with behavior syntax.

macMikey commented 4 years ago

one other thing that i just discovered - with behavior only allows you to reference other SOS's. You can't reference buttons for instance, so for legacy apps that are using references to button behaviors (e.g. apps using tmc2, or some dg's, because i think there are some button script behaviors), with behavior won't help.

trevordevore commented 4 years ago

The stackFiles property of the Levure app stack is always accurate as it is built when the application is loaded. So in this case we can leverage the stackFiles for the stacks in the root behaviors folder without worrying that it will get out of date.

I'm not familiar with Navigator but it sounds like you are working with UI stacks and not stacks that would go in the behaviors folder. It sounds like code is generated that would go in the preOpenStack handler. Am I correct?

Can you provide an example where a legacy application would need to store a script only stack in the root behaviors folder and would need to reference a control within a stack as a behavior? Ideally Levure should never add messages that aren't really necessary. Sending loadBehavior to stacks in the root behaviors folder was a workaround for an engine limitation that has since been lifted. In addition, if Levure starts assigning those stacks to the app stack stackFiles property as described previously then the "behavior object isn't loaded into memory so fails to be assigned as a behavior" issue goes away as well.

macMikey commented 4 years ago

Recall that with behavior can only reference stacks. It cannot reference buttons. The problem in this case is accommodating a legacy product and a legacy technique, namely behaviors that are in buttons in another stack. The example that immediately comes to mind is tmControls. Remember that the use case is extracting scripts from existing objects and putting them into SOS's.

Screen Shot 2020-06-27 at 21 53 12

The interface objects are groups that contain a number of objects, like fields, graphics elements (like backgrounds and foregrounds) and icons. The script for each object resides in the group, and thus would be extracted into a ui behavior. Below is the "Settings" button, exploded, in the PB.

Screen Shot 2020-06-27 at 21 54 15

Below you can see the chain. The first behavior is the script that was previously in the group, but is now a separate UI behavior

Screen Shot 2020-06-27 at 21 54 25

The second behavior is a button script for this type of object (a pushbutton)

Screen Shot 2020-06-27 at 21 54 30

The third behavior is also a button script, the master behavior

Screen Shot 2020-06-27 at 21 54 33

Each of the object types gets its behavior from a button in a separate stack. Each of those buttons inherits the "master behavior" from another stack. There is a third separate stack that has the behavior and design elements for the table object.

Screen Shot 2020-06-27 at 21 56 32 Screen Shot 2020-06-27 at 21 56 39

Below is the exploded PB for those two stacks.

Screen Shot 2020-06-27 at 21 56 51

Each UI stack has its own separate tmc behaviors, uniquely named, as you would expect, so that LC is not confused by the names being the same, and so that unloading one UI stack does not cause the others to lose their behaviors.

What I did to make this work was:

  1. Remove the tmc behavior and template substacks from the main stack.
  2. Put those stacks in the /app/behaviors folder to ensure that they are in memory when the ui loads
  3. In the preopenstack handler for the ui stacks, walk the ui stack's stacksinuse and assign each of the controls' behavior buttons. I actually ended up writing loadBehavior routines for each of those stacks and then invoking them.
trevordevore commented 4 years ago
  1. It sounds like the stacks you add to the ./app/behaviors folder do not need to have any modifications made in a LoadBehavior routine. They are placed there so that the are automatically loaded into memory when the app loads. Correct?

  2. Is it correct that you converted your group scripts (e.g. the Settings group script) to SOS and the behavior of each group SOS needs to point to a tmc behaviors which is a button? It sounds like it is only your SOSs that need to have their behaviors assigned when the UI stack loads.

To me it sounds like your situation is best handled in the preOpenStack of the UI stack, like you are doing now. You are leveraging the ./app/behaviors folder to make sure a global behavior is always available and then coding a workaround necessary for using SOSs with parent behaviors that are buttons.

I would prefer to do away with Levure sending a LoadBehavior message entirely as I don't think it is a message that the framework should be synthesizing. I feel that the engine should be sending a message when a stack is loaded into memory. I also do not want to extend it to the behaviors associated with UI stacks. Levure doesn't load UI stacks into memory when the app launches. This improves memory usage and allows a developer to manage which UI stacks stay in memory using destroyStack. To add a LoadBehavior message for UI stacks Levure would have to monitor when a UI stack was being loaded into memory and then send the LoadBehavior message to the behaviors at that time.

macMikey commented 4 years ago

I don't think that's a bad decision. I think that grasping the Tao of Levure is something that adopters have to do, and part of the Tao is "Less".

trevordevore commented 4 years ago

👍🏻