Bubble

(Engine-Level Function)

Description: Starts a new child bubble and launches the given module into a new script thread in that bubble.
Returns: Bubble handle to the started child bubble.
Usage: Script Only.
Function Groups: Bubble and Thread
Related to: BubbleList | BubbleQueueLength | BubbleReceive | BubbleSend | RunInBubble |
Format: Bubble(BubbleName, Module [, ImportDictionary])
Parameters:  
BubbleName

Required text. The name that is to be associated with the bubble as well as the name given to the script thread that is started in the bubble.

Displayed in the user interface of debugging tools.

ModuleOrObject
Required object. This is the root module that is loaded and launched into the bubble. It is always loaded and launched with no parent or caller.
ImportDictionary

Optional dictionary. May be used to specify a subset of variables or modules from the ModuleOrObject parameter that will be loaded, launched, or both, into the child bubble rather than loading and launching the entire module. This is of use when you want to share the same code between modules running inside and outside of the child bubble.

When an ImportDictionary is provided, the entire Module is not loaded into the bubble to be run. Instead, a copy of ModuleOrObject is reconstructed in the bubble with none of its states and with only the subset of variables specified in the ImportDictionary. This is then launched in the bubble.

In this situation, the root module is like a library of modules and objects that you want to selectively import into the bubble, with the actual bubble worker module being one of the modules imported.

The keys to the dictionary are the names of variables in ModuleOrObject, and the values can be any one of the following:

  • If the key specifies a variable containing a module or object, then setting the value to FALSE means that the module will be fully loaded into the bubble and placed in the corresponding variable, but it will not be launched.
  • If the key specifies a variable containing a module or object, then setting the value to TRUE means that the module will be fully loaded into the bubble and launched, with the object being placed in the corresponding variable.
  • If the key specifies a variable containing a module or object, then you can set the value to a nested ImportDictionary. A nested ImportDictionary is the same as the top-level one, except that it operates on the module or object specified in the dictionary key. The resulting reconstructed object is placed in the corresponding variable.
  • If the dictionary value is Invalid, then any packable value is imported into the bubble and placed in the corresponding variable. Note that static arrays are converted to dynamic arrays as a part of the import process.

 

Any local variable in the object or module can be imported, regardless of whether it is protected.

It is typical to have one fully loaded and launched module that will perform the work that the bubble has been created for. The other imported modules and variables would be common code that the bubble worker module uses and which is also used outside the bubble.

Imported variables are populated inside the bubble in the order that they were added to the import dictionary. Therefore, if (for example) an imported and launched module has a Constructor that references another imported module, you will need to add the other module to the import dictionary first so that it is ready to be used when the Constructor runs.

Comments:

The Bubble function only loads and launches modules into the new bubble. It will often be necessary to provide input data values to the new bubble. These can be sent after the Bubble call using the BubbleSend function.

Modules that are loaded into a child bubble are loaded independently, and therefore do not share "Shared" variables with the copy of the module that exists in the parent object. Bubbles must refrain from setting retained values that could be set in another bubble. They must use SetInstanceName to give their object a unique name before setting any instance variables. At the same time, parent bubbles should refrain from writing to the retained variable using the default instance name in a module that could be loaded inside a child bubble. Otherwise, they risk corrupting the retained value, or loading a partially-written retained value

The bubble will continue to exist for as long as there are objects running within the bubble. After the last object within the bubble in slain and all outstanding related work within the bubble is finished, the bubble will tear itself down. Note that slaying the object that started the bubble will not stop the bubble. The bubble must tear itself down, either on its own or upon receipt of a message from its parent bubble telling it to do so.

The bubble handle returned by this function is not invalidated when the bubble tears itself down. If a parent bubble needs to know when a child bubble is finished, then the child bubble should send the parent bubble a message before terminating. A parent bubble does not need to retrieve that message before the child bubble terminates. This is a reason why the bubble handle is not invalidated when the child bubble finishes, as the parent bubble may still need to retrieve one or more messages via the child bubble handle.

When ImportDictionary is not provided, ModuleOrObject will only be successfully loaded into the bubble if it has no static references to external variables since it is loaded with no parent. When ImportDictionary is provided, any modules that it fully imports (dictionary value TRUE or FALSE) cannot statically reference any external variables that are not also being imported, or else they will fail to load in the bubble. So, if you are importing ModuleA and it contains a reference to ModuleB, you’ll need to import ModuleB. In order to do that, you’ll also need to import all variables that ModuleB references. Hence, even if your bubble only needs to call one external module directly, you may need to import a number of modules and variables in order to be able to use that one module.

Example:

{ A simple single-module bubble: }
<
RunMyProcessInBubble
(
  Input                       { Input to a CPU-intensive process           };
  pResult                     { Result of CPU-intensive process            };
)
[
  MyProcessModule Module;
  BubbleHandle;
]

Init [
  If 1 WaitForResult;
  [
    BubbleHandle = Bubble("MyProcess", \MyProcessModule {use backslash to avoid implicit launch});
     { Send the input data to the bubble. Since MyProcessModule is running in its own bubble,
       it doesn't have direct access to any variables in this module - all data communication
       must be via the BubbleSend function. }
    BubbleSend(BubbleHandle, Input);
  ]
]

WaitForResult [
  If BubbleQueueLength(BubbleHandle) > 0;
  [
    *pResult = BubbleReceive(BubbleHandle);
    Slay();
  ]
]

<
MyProcessModule
[
  ReceivedInput;
  Result;
]

Main [
   { Receive the input data that the parent immediately sends after bubble creation. }
  If BubbleQueueLength() > 0;
  [
     ReceivedInput = BubbleReceive();
    { ... Perform a long CPU-intensive process using ReceivedInput, computing Result ... }

     { Send the parent bubble the result. }
    BubbleSend(Result);
     { This is the only object running within the bubble, so the bubble will tear itself down
       shortly after this Slay. }
    Slay();
  ]
]
{ End of RunMyProcessInBubble\MyProcessModule }
>
{ End of RunMyProcessInBubble }
>
{An example that uses ImportsDictionary, where the bubble is managed by a
 service and wants to use other module/variables in Code: }


MyService [ Destructor Module; HelperModule Module; BubbleWorker Module; Imports; BubbleHandle; ] Init [ If 1 WaitForResult; [ Imports = Dictionary(); Imports["CodeLevelHelperModule"] = FALSE { Module defined in AppRoot.SRC, used by BubbleWorker }; Imports["MyService"] = Dictionary(); Imports["MyService"]["HelperModule"] = FALSE { Module used by BubbleWorker }; Imports["MyService"]["BubbleWorker"] = TRUE; BubbleHandle = Bubble("MyServiceBubble", Code, Imports); ] ] Main [ { Send/receive messages to/from bubble. } ] < Destructor Main [ If 1; [ IfThen(Valid(BubbleHandle), { Inform the bubble that it must destroy itself. The value we send doesn't matter as the only message that the bubble expects on the "DestroyBubble" named queue is the self-destruct message. } BubbleSend(Invalid, BubbleHandle, "DestroyBubble"); ); Return(); ] ] { End of MyService\Destructor } > < BubbleWorker Main [ { Send/receive messages to/from parent bubble. The code makes static references to CodeLevelHelperModule and HelperModule. } { Listen for the parent bubble telling us to self-destruct. } If BubbleQueueLength(Invalid, "DestroyBubble) > 0; [ { No need to receive the message. The only message we receive on the "DestroyBubble" named queue is the message to destroy the bubble, so the message content doesn't matter. } Slay(Self, 1 {must slay parent objects to stop bubble}); ] ] { End of MyService\BubbleWorker } > { End of MyService }