|
It is possible, with no understanding of multithreading, to create impressive applications in MediaForge. You will soon discover, though, that a clear understanding of the authoring systems multithreaded architecture is worth pursuing. Multithreading is just software jargon for doing more than one thing at a time. MediaForge is inherently multithreaded, object oriented and event driven - a difficult mixture to manage. There are many things contained in the MediaForge engine and stage manager that together isolate the complexities of this environment from the developer. Each object is capable of running in its own separate thread, independent of other objects. Even entire MediaForge scripts can run in their own thread. However, threading entire scripts is seldom necessary. Later, you will learn about a nice little feature in MediaForge that makes it easy to handle events fired by threaded objects. It has long been an axiom of mine that the little things are infinitely the most important. -- Sir Arthur Conan Doyle MediaForge scripts are linked lists of powerful objects. The project script is normally the first script processed when a project is run. This is true even if when doing non-linear testing of some arbitrary script through the use of the "Play Scene and Parents" feature on the Play pulldown menu. This is a very nice feature, and definitely useful in testing individual scripts and objects in a large project. But let’s examine the types of scripts that can be created in MediaForge and see how the threading architecture applies. Scripts currently available in MediaForge are: Project, Background, Scene and FreeStyle. Multiple scripts can be edited at the same time within the MediaForge Workbench. With the exception of the Project Script, each edit window looks pretty much the same. The project window contains a tree structure of the entire project where each script is listed according to its hierarchy and purpose. This project tree is probably the best place to access scripts. I like to double click on a node to edit the associated scene or background. A button on the Workbench entitled "Show Project Tree" can always bring the project tree back into view. You can also right click from within a scene or background and the is an option on the right click menu to bring you back to the project tree as well. Using the right mouse button on the project tree will let you quickly add new scripts to the project and even change the pathname for any script, including the project script. This is the best way to rename a script(or even an entire project) because house keeping is performed here. House keeping will change the name of the script within the project as well as rename the script (.mfs) file. Also, and this is important, using right-mouse menu from the tree will rename any associated files including the MediaBasic files and the Project Standbys. Now back to threading and how it relates to these scenes, backgrounds and freestyles When a project is run from the Workbench, or even from the Runtime Player, a new thread is created for the Project Script. This is good to understand because a project runs in a thread that is separate from the Workbench and the Stage. Put another way, the Stage on which all objects are displayed exists in a thread that is separate from the project script. The Stage and Workbench thread is termed the "Main Thread." The thread in which the Project script resides is termed the "Project Thread." The great thing about this approach is that all maintenance of the stage, such as Windows Message handling and painting is performed within the Main Thread, while the MediaForge project is free to perform its own processing, memory management and object event handling in its own thread. So what about the other scripts: Background, Scene and FreeStyle scripts. Both the Backgrounds and Scenes always run in the Project Thread along with the Project Script. As you move from one scene to the other within the Project hierarchy, new background and scene objects replace or mingle with the existing ones. If you transfer to a new scene that shares a common background(s), then the objects in the shared background(s) remain on the stage. How then does additional threading occur within a MediaForge Project? The answer is with FreeStyle scripts and threaded objects. When a FreeStyle script is called, it acts like a subroutine, running in the same thread and returning when complete. On the other hand, when a FreeStyle script is threaded, control returns immediately and the FreeStyle script continues to run happily in its own thread. This type of thread is termed a "FreeStyle Thread" within MediaForge. Another type of MediaForge thread is the individual object’s thread. Any object is requested to run in its own thread by simply checking "Thread Object" checkbox on the Object Builder’s Events Tab. There is no artificial restriction on the number of "Object Threads" that can be created within MediaForge. Some objects, such as the Timer Object, are always threaded. Other objects, such as the Compare Object, are never thread. If you cannot uncheck the threading option on the events tab, then the object is always threaded. On the other hand if you cannot check the threading option, then the object is never threaded. Otherwise, with most objects, the choice is yours. The final type of thread within the MediaForge architecture is more subtle. This thread is termed an "Internal Thread." These threads get created because it is possible for some objects to have an associated sound. If an object has a Sound Tab available on its Object Builder, then a sound for that object can be assigned and controlled with event actions and with the Flow Control Object. The Actions used to control associated sound are "Play this object’s sound," "Play sound in object," "Stop this object’s sound," and "Stop sound in object." If the "Play Threaded" option is checked on the sound tab for the object, then an Internal Thread is created for playing the object’s sound. Inside MediaForge an associated sound is just a sound object linked to some other object, so in a way, an internal thread is just a fancy object thread. If the associated sound is not requested to play threaded, then the sound plays in the same thread in which the object is running, either the Project Thread, FreeStyle Thread, or Object Thread. At first glance you might question the intellectual importance of these thread internals since MediaForge creates and manages threads automatically for you. Its nice to know that MediaForge is so robust, but so what. The reason for thread understanding involves the other ingredients of the mixture that were mentioned earlier: Objects and Events. Later I will explain how objects in one thread fire events that may be handled in other specific types of threads and explain why this is necessary. In summary then, the following types of threads are created in MediaForge: Main Thread - The application thread where the Stage and Workbench reside, and where normal windows messages, such as mouse events are first handled. Project Thread - The thread where the Project, Backgrounds and Scenes run. FreeStyle Threads - Threads where FreeStyle scripts run when they are threaded. Object Threads - Threads where individual objects run when they are threaded. Internal Threads - Threads created for threaded sounds associated with individual objects. Windows 95 introduced the world to preemptive multitasking where the operating system decides when to take the processor away from the current application and give control to another application. This has not changed much in subsequent releases of windows including Window XP. Preemptive multitasking makes it easy for us to run more than one program at the same time. You can think of threading in the same way. An application that has multiple threads running is kind of running many applications inside the base application. Within MediaForge though, each application can be thought of as objects performing their own tasks. The trick is letting them communicate with each other, with the project, with windows itself and with the stage on which they perform. May you live all the days of your life - Johnathan Swift When an object is taken from the generic Object Palette and placed into a script, it then exists, of course, as part of the script. But when a project is played, the object takes on a new life cycle. It is copied or cloned, if you will, for the purpose of being run. Plato believed that we mortals were simply copies of a true consciousness that exists somewhere else. Plato’s dilemma was that he, the copy, conceived the very notion. Aside from his knowledge of dreams, he had no concept of Virtual Reality, and the possibility that man could someday create machines that would help them exist in more than one place at the same time. This is fun to think about but we should return to our original topic, Inside MediaForge. Inside MediaForge, the code responsible for running a project is referred to as the engine. The engine loads scripts, creates threads and clones copies of the script’s objects in the threads in which they will run. Although MediaForge is written in Visual C++ and MFC (the Microsoft Foundation Class Library), which is touted by Microsoft to be re-entrant (code capable of being used by more than one thread at the same time), the developers of MediaForge, starting with version 3.0, elected to make things predictably stable. MediaForge objects can be just about anything: Movies, Sound, ActiveX controls, DLLs, Audio Visualizations and so on. The possibility that everything will always be completely thread safe is pretty remote. DirectX COM components alone have crashed Windows enough times to frustrate the world and certainly justify a cautious approach; one of those little things added to MediaForge that is infinitely important. The developers chose to guarantee that each object is instantiated - brought into existence or cloned - within the same thread in which it will run. Events fired by one object can act on objects in other threads through the use of a technique known as marshaling. Marshaling is just an arrogant term, used by sophisticated programmers, to describe sending a packet of information from one thread to another. This packet contains enough information to let the receiving thread perform the desired task. When a project is run, objects exist when their script is loaded and cease to exist when the script finishes. There are two scripts that will always be loaded when a project is running, the Project script and the Standbys script. Objects in those scripts will exist the entire time the project is running. The life of any object can take on the following characteristics: I exist because my script is loaded, and then I am loaded, I am visible (if possible), I am playing, I am animating (if possible), I am paused, I am hidden, I am stopped, and finally I am just gone. As long as an object exists, it can be played, hidden, shown, stopped and so on repeatedly, but once an object is gone, it is no longer available to the project unless its script is loaded again. Objects have Initial Behavior that is selected on their Object Builder Events tab. This initial behavior determines how much the object is allowed to do when it is first encountered. Objects are normally encountered top to bottom when a script is run. This top down flow can be altered with the use of the Jump Object but my purist style restrains me from using the Jump Object since anything achieved with the Jump Object can be done better another way. The Standbys script has been mention a few time, but what exactly is the Standbys script? The poor Standbys is often misunderstood. It’s like the planet Pluto, no one is quite sure if it is really a planet or an asteroid. The same is true of the Standbys because most people are not quite sure if it is really a script or something less important. In my opinion, the Standbys is a script, but a script that never gets run; it just exists within the Project Thread. The objects in the Standbys cannot be assigned an initial behavior because they are never actually encountered. Instead a standby object must be activated with an action. Standby objects are semi-initialized enough to download their data when used over the internet, but they are not fully loaded into memory like an object that is encountered. Standbys are not truly loaded until an Action is used to Initialize or Play them. Starting in MediaForge 5 though there is now an option to "Auto Initialize" standby objects. This was done to facilitate shared resources, discussed later in this document. You should place objects in the Standbys if they will be used randomly. A large array of objects can be stored in the Standbys waiting for someone to use them without taking up virtual memory until they are actually used. Playing a sound object in the Standbys, for example, will be somewhat slower the first time, unless auto initialized, since the sound must first be loaded before it can be played. Subsequent plays will be faster. Of course, you could initialize the object sometime prior to playing. But if you have objects that will definitely be used within the project as a result of events that occur in various scenes, then you should consider placing the object in a common Background script or in the Project script where it will automatically be initialized, or just check the auto initialize option on the Events Tab for a Standby object. Placing an object in the Project script and restricting its initial behavior by having it wait until played, is equivalent to placing the object in the Standbys and checking the auto initialize option. So now we have established that objects exist when their script is loaded and they no longer exist when the script ends. We also know that objects come into existence in the same thread in which they will be used. So how can an object that exists in some arbitrary thread, be used from an event fired in a different thread? The answer is the automatic marshaling features in MediaForge; marshaling is a little feature that makes large projects much easier to manage. With a basic understand of thread types, marshaling can now be covered in detail. Knowing how and where actions are sent will help you understand the timing and sequence of those actions. Read the previous sentence again. Does it make any sense at all? Probably not so let’s consider an example. There is a button on the stage that when pressed will call a MediaBasic project subroutine and when released will play a sound object. The sound object just happens to be threaded. The button object exists in the Project Thread but all mouse actions on the stage are handled in the Main Thread. So the mouse down event is fired in the Main thread and the action to call a project subroutine is marshaled to the Project Thread. The mouse is released in the Main thread and an action to play the sound object is marshaled to the Sound Object’s Thread. So which action occurs first, the subroutine call or the request to play the sound? The subroutine call is probably first, but there is no guarantee because the Project Thread might be busy processing some other action, while the Object’s thread is just waiting for a chance to start playing. Does the sequence matter? I don’t know, only you the author can know the answer to that question. The sequence may not matter, but MediaForge supports indirection, so if the object to be played is contained in a global variable and the project subroutine is used to set that variable, then you could end up playing the wrong sound or not playing any sound. Assuming that actions will be processed in the same sequence as the events that fired those actions is a bad assumption to make in any multithreaded environment. The mouse down call subroutine - mouse up do something scenario might leave you confused at the results. A much better approach would have been to simply call the subroutine when the mouse is released and have the subroutine play the Sound Object before the subroutine returns. This way the needed subroutine code can always get run before the Sound Object is played. With this knowledge let’s examine how marshaling works in MediaForge. Any action requested as the result of an event being fired, or as a result of the Flow Object being encountered will channel that request through the Marshaling Manager within the MediaForge engine. Actions that result in error conditions will sometimes display messages when the project is run from the workbench, but the same request will usually be ignored if run using the Runtime player. In either case, error conditions are posted in the MediaForge reserved word variable "rwLastError." This variable is tested with the Compare object or with extensions within MediaBasic, DLL code and ActiveX code. You can also examine and change MediaForge variables and reserved words from the MediaForge COM object. To test results using this reserved word, you should first set it’s value to 0(No Error), because MediaForge only initializes it when the project first starts running, and when a new scene is loaded. The following is a list of error conditions that will be posted in rwLastError following each Action request: No Error = 0, the request was completed without error. Duplicate Error = 1, caused by an attempt to GoTo the same scene. Missing Error = 2, caused by an attempt to use an object, script, label or file that is missing. Threading Error = 3, caused when an attempt to create a thread fails or when there is an attempt to make requests on a script after the thread ended. Error Message = 4, caused when an error has occurred that has created an error message, or would cause an error message if running from the Workbench. Busy Error = 5, caused by an attempt at concurrent usage of the same script within different threads. Wrong Category = 6, cause by an attempt to use the mfoPutOnStage extension with a non-visible object. No DirectSound available = 7, caused by an attempt to enable DirectSound when it has no been installed or is not available. Sound Error = 8, caused when a sound file cannot be loaded. Actions are the choices found on the Events tab and on the Script Flow object under the "Do The Following:" category. Actions in the Events tab are how you always handle events in MediaForge. For every event you can choose an action to deal with that event. The table below outlines how the actions are marshaled and provides an inside look at what each action does. The actions in the table are listed in the same alphabetical sequence as the actions in MediaForge for easier reference. Although Table 1 shows the "Target Thread," if the target thread happens to be the same as the thread in which the event is fired, then no marshaling is performed and the action request is performed directly. It is possible for some events, such as mouse events, to fire from the Main Thread even though the event object is in another thread. In reading this table, the "event object" is the object that defined the event and the corresponding action. The target object is the name of an object used in some of the actions such as the Play object. One final note on Table 1, if the Action is requested by the Script Flow object instead of through an event, then the Script Flow object should be consider the event object. Please glance through the table below . This table is just a reference table but it is helpful to know where to find this information. (Table 1) | ACTION | TARGET THREAD | | Advance script | The Advance is sent to the thread in which the target script resides. A waiting script will continue from the position of the wait. If the script is a ready scene, waiting for requests, then this request will terminate the project. | | Advance this thread | The Advance request is sent to the event object’s thread. A waiting script will continue from the position of the wait. If the script is a ready scene, waiting for requests, then this request will terminate the project. | | Call FreeStyle script | The Call request is sent to the event object’s own thread. A subroutine like call is made to the FreeStyle script. Once the FreeStyle script finishes running all objects in the FreeStyle Script are destroyed. If the Call was made using a Flow Object instead of an event, then control returns to the next object in the calling script following the Flow Object. | | Call MediaBasic project subroutine | If the event is fired from the Main Thread, such as a mouse event, then the request is sent to the Project Thread. Otherwise, the subroutine is called without Marshalling. Unlike MediaBasic script subroutines, MediaBasic Project subroutines can be called from any thread. MediaBasic Project files have the same name as the Project but with the MediaBasic file extension of .ebs, and .ebc for the compiled code. When the subroutine ends, control returns back to the MediaForge engine. | | Call MediaBasic script subroutine | If the event is fired from the Main Thread, such as a mouse event, then the request is sent to the Project Thread. Otherwise, the subroutine is just called without marshalling. If the event is not fired from the Main Thread or the Project Thread, then an error message is displayed. MediaBasic script files have the same name as their associated script but with the MediaBasic file extension of .ebs, and .ebc for the compiled code. When the subroutine ends, control returns back to the MediaForge engine. | | Clear The Stage | The Clear Stage request is sent to the Project Thread. This action hides all objects on the stage, it does not remove the objects. It also clears the Backdrop of any objects that had been moved to the backdrop. | | Disable object | The Disable object request is sent to the target object’s thread. Disable will stop and disable the object and prevent any events the object has from firing. | | Enable object | The Enable object request is sent to the target object’s thread. Enable will enable disabled object. Paused objects will not be enabled. The object cursor is reset. | | GoTo FreeStyle script | This request is sent to the thread of the object that fired the event,, unless the object is a threaded object, in which case it posts the request to the current scripts thread. This action causes the active FreeStyle script to abort early by effectively jumping to the end of the script. The new script then starts running from the beginning. If the original FreeStyle script was called, the point of return will be the same when the replacing FreeStyle script ends. If the original script was threaded then the new script runs within the same FreeStyle Thread. | | GoTo next scene | The GoTo request is sent to the Project Thread. This transfers to the next scene in the project tree. If the scene is the last scene in the project tree, then this request is ignored. Objects in the old Scene are automatically removed along with objects in any Backgrounds that do not belong to the new Scene. | | GoTo prior scene | The request is sent to the Project Thread. This transfers to the prior scene in the project tree. If the scene is the first scene in the project tree, then this request is ignored. Objects in the old Scene are automatically removed along with objects in any Backgrounds that do not belong to the new Scene. | | GoTo scene | The request is sent to the Project Thread. This request transfers to the target scene. If the scene is not in the project tree, then this an error message is displayed. Objects in the old Scene are automatically removed along with objects in any Backgrounds that do not belong to the new Scene. | | GoTo URL | The request is sent to the Main Thread. GoTo URL issues a ShellExecute open on the URL or file name. The current project does not terminate as you might suspect, but when running through ~Mirage, a transfer to a different URL can result in the project terminating at the request of the internet browser. If the request succeeds and a separate application is started, then the handle of the shelled process is stored in the rwhShelledProcess reserved word variable. This handle can be used at any time to terminate the process with the mfTerminateProcess MediaBasic and DLL extension. The GoTo URL is a great way to launch other applications if you also want to terminate the application from MediaForge. | | Halt all other threads | If the event is fired from the Main Thread, such as a mouse event, then the request is sent to the Project Thread. Otherwise, the request is sent to the event object’s thread. This action will halt all threads except the current thread, the Project Thread and the Main Thread. The current thread will wait without processing any actions until the other threads are down. | | Halt current thread | The Halt request is sent to the event object’s thread. The current thread will be halted, and any scripts running in the thread will be removed along with all of their objects. If the current thread is the Project Thread, then the project will terminate. The best way to terminate the project is with the Halt project action. | | Halt project! | The request is sent to the Project Thread. The Halt project disables DirectSound, purges all marshaled actions, disables stage painting, ends all Object Threads and Internal Threads, ends all script threads and then ends the Project Thread. After the project halts, stage painting is enabled again. | | Hide object | The Hide request is sent to the Main Thread, unless the target object is a windowed object, such as an RTF object or a non-sprite Movie object, in which case the action request is sent to the target object’s own thread. This action hides the object’s stage sprite by effectively removing it from the refresh list so that the Stage Manager will not request rendering of the object. The Stage cursor is reset to reflect where it is sitting after the object is hidden. Aside from being invisible, the object is still present and active. | | Hide this object | The Hide this object request is sent to the Main Thread, unless the event object is a windowed object, such as an RTF object or a non-sprite Movie object, in which case the action request is sent to the event object’s own thread. This action hides the event object’s stage sprite by effectively removing it from the refresh list so that the Stage Manager will not request rendering of the object. The Stage cursor is reset to reflect where it is sitting after the object is hidden. Aside from being invisible, the object is still present and active. | | Initialize object | The Initialize request is sent to the target object’s thread. If a thread does not yet exist for the object, as may be the case with a threaded object in the Standbys, then a thread is created, a copy of the object is created in the new thread and the Initialize Object request is sent to the new thread. Objects are normally initialized when they are encountered in the script. Some objects that reside in active scripts are never encountered. The objects in the Standby script are never encountered and consequently are not fully initialized and loaded until they are shown or played. Other objects that are not in the Standby script that are skipped using a jump will never be encountered. The Initialize Object action will initialize an object that is in an active script but has not been encountered and previously initialized. The typical use for the Initialize Object action is to improve the reaction time of an un-encountered object, such as a Standby object, when it is first played. Standby scripts are preloaded, but preloading a script does not fully load its objects. These objects are localized in the case of remote content, then loaded into Virtual Memory, and then released. To fully load a Standby object as if it had already been played once, it needs to be initialized. Usually Standby objects will load and respond fast enough to normal event request, but if immediate response is required, such as a Standby Sound connected to a button, it may be desirable to Initialize the object. | | Jump to label | The Jump request is sent to the event object’s thread. Objects are normally encountered top to bottom within a script when it is run. This action is used to change the sequence that the objects are encountered. This request will cause an error message if it is used from an Object Thread. The Jump object provides more control over jumping within a script than the Jump to label action. | | Launch External Executable | The Launch request is not marshaled and is performed immediately. The Launch is performed with the Windows CreateProcess command. It then waits for advance or for the external application to complete. | | Leave this FreeStyle script | The request is not marshaled and is performed immediately. This request is similar to jumping to the bottom of the script except that any object running or waiting for events will be aborted. If the FreeStyle script was called, then control will return to the point of the original caller. If the FreeStyle script was threaded, then the thread ends along with the script. | | Move object to cursor | The request is sent to the target object’s own thread. This will cause the target object to move to the mouse pointer. If at the time of the move, the mouse is dragging, then there is no time delay set for the movement. Otherwise, the delay time, in milliseconds, is equal to 2.5 times the distance to move in pixels. You can use this action to drag objects on the Stage by making the target object the same as the event object. For more responsive dragging you should also use this action with the mouse drag event on objects behind the target object. This keeps the object dragging even if the mouse moves faster than the system can move the object by tracking its off object location. | | Pause all objects | The Pause request is sent to the Main Thread. This will suspend all threads, except the Main Thread. Mouse events can still be used to resume the paused threads. | | Pause object | The request is sent to the Main Thread. This will cause the thread in which the object is running to be suspended, unless the target object is a Sound object or Movie object, in which case the thread continues to run while the sound and video themselves are paused. | | Pause this object | The request is sent to the Main Thread. This will cause the thread in which the event object is running to be suspended, unless the event object is a Sound object or Movie object, in which case the thread continues to run while the sound and video themselves are paused. | | Play object | The Play request is sent to the target object’s thread. If a thread does not yet exist for the object as may be the case with a threaded object in the Standbys, then a thread is created, a copy of the object is created in the new thread and the Play Object request is sent to the new thread. This will cause the object to start playing. The object must reside in an active script and may or may not be currently initialized. If the object is not currently initialized, then the object will first be initialized and loaded and then played. Playing a visible object will not hide and redisplay the object, but if the object is hidden, then it will be displayed using any show effect selected for the object. If the object is a visible object and has a path, it will move along the path each time it is played. For the Sprite object, center frame cycling is performed after the object moves along its path. For MediaBasic objects, DLL objects and ActiveX objects their mfoAnimate routine is called, if they have one, following movement along their path. Sound and Movie objects must be stopped before they will start playing their sound or video again as a result of the Play object. This is a complex action, so experimentation is probably the best guide. | | Play sound in object | The request is sent to the target object’s thread, or if the sound is threaded then the request is sent to the target object’s internal sound thread. The sound identified on the target object’s sound tab will be played. The first time the sound is played, it will create the internal thread, if needed, and it will load the sound. | | Play this object | The request is sent to the event object’s own thread. See the Play object above for more details. | | Play this object’s sound | The request is sent to the event object’s thread, or if the sound is threaded then the request is sent to the event object’s internal sound thread. The sound identified on the event object’s sound tab will be played. The first time the sound is played, it will create the internal thread, if needed, and it will load the sound. | | Preload script | The request is sent to the event object’s thread. This request will partly initialize all the objects in a script. A clone of each object is created, initialized and then deleted. This loads the object’s data into virtual memory and fully downloads object data over the internet where it is stored in temporary file(s) on the client computer. Downloading and storing of objects would happen normally when the objects are initialized, but sometimes with Internet projects it is desirable to pre-download data a scripts data in a separate thread while the user is using other parts of the project. The most common use of this action is to pre-download script object data when running through ~Mirage over the internet. | | Print object(alone) | The Print request is sent to the Main Thread. The object is printed at its default size alone with no other objects. | | Print object(domain) | The request is sent to the Main Thread. The rectangular area or domain where the object is positioned on the stage will be printed. A temporary and separate large stage is created for this action. All objects within the domain are enlarged, scaled and added to the print stage for printing. This is a nice and unique feature in MediaForge, because if there are text or vector images being printed in the domain, they will not loose their quality by having their smaller bitmap representation stretched for printing. | | Print stage | The request is sent to the Main Thread. The entire stage is printed. A temporary and separate large stage is created for this action. All objects on the stage are enlarged, scaled and added to the large print stage for printing. This is a nice and unique feature in MediaForge, because if there are text or vector images on the stage, they will not loose their quality by having their smaller bitmap representation stretched for printing. | | Remove all objects in script | The request is sent to the event object’s thread. This will remove all objects in the target script from the stage. Removing an object also un-initializes the object and frees the object’s data from virtual memory. I had to do some digging into this action to find out what its purpose was. It turns out that the proposed use for this action is to remove all objects from an active background script. This action should never be used with the event object’s own script as it will hang your project in MediaForge 3.5 Build 3 or lower. Later releases just display a warning message and ignore the request. It seems that a Hide all objects in script would have been more useful since it could then be used on the event object’s own script. | | Remove object | The Remove request is sent to the target object’s thread. This will remove a visible target object from the stage, un-initialize the target object and free the object’s data from virtual memory. If your intent is to just remove the object from the stage and later show it again, then you should use the Hide object instead. A removed object cannot be shown, but it can be played again since the play action will re-initialize the object. | | Remove this object | The request is sent to the event object’s thread. This will remove a visible event object from the stage, un-initialize the object and free the object’s data from virtual memory. If your intent is to just remove the object from the stage and later show it again, then you should use the Hide this object instead. A removed object cannot be shown, but it can be played again since the play action will re-initialize the object. | | Restart project | The request is sent to the Project Thread. This will halt the project and start it again from the beginning. | | Resume all paused objects | The Resume request is sent to the Main Thread. This causes all paused objects and their threads to start again from where they were paused. | | Resume paused object | The request is sent to the Main Thread. This will cause the target object and its thread to start again from where it was paused. | | Resume this paused object | The request is sent to the Main Thread. This will cause the event object and its thread to start again from where it was paused. | | Retreat this script | The request is sent to the event object’s thread. This action aborts any waiting object and starts running the script with the prior object. Using this action along with the Advance this script, you can build slide shows quickly. Just check mark the Shown event for each Image object in the slideshow script and use the Wait for advance action. Events, such as mouse button events can then be used to advance and retreat the slide show. | | Run new project | The request is sent to the Project Thread. This action us used primarily for Web applications. The request will halt the running project and start running the new project. When using the runtime player or ~Mirage, new consolidated .mfg files can be run. Consolidated .mfg files cannot be run using this action from the Workbench and a message will result if you try. This action provides a great way to produce modular Web projects since each new .mfg file will be downloaded separately over the internet, and each .mfg can hold entire projects and their objects and data, including custom DLLs, in compressed form. The trick is to break your Web application into strategic .mfg modules using the MediaForge collector. Once a consolidated .mfg file is downloaded, it will remain useable in the browsers cache, like any other file, until it is purged by the browser according to the users settings for the browsers temporary files. If your modules each use the same stage size you should use the "Keep the same stage, for quickness, when running other projects" option on the Stage Attributes Playback tab. This same stage option will let MediaForge provide a smooth transition between projects. | | Show hidden object | The Show request is sent to the Main Thread unless the target object is a windowed object such as an RTF object or a non-sprite Movie object, in which case the action request is sent to the target object’s own thread. Use this action to show a visible object that is currently hidden. If the object has a show effect, then it will not be used. To re-display a hidden object using its show effect, use the Play action. | | Show this object | The Show this object request is sent to the Main Thread unless the event object is a windowed object such as an RTF object or a non-sprite Movie object, in which case the action request is sent to the event object’s own thread. Use this action to show the visible event object if it is currently hidden. If the object has a show effect, then it will not be used. To re-display a hidden object using its show effect, use the Play action. | | Stop object | The request is sent to the Main Thread. This action will stop the target object from playing. This action is capable of stopping visible objects while displaying with show effects and can stop objects while moving along a path. When sound and video are stopped, then subsequent plays of the object will start the sound and video from the beginning. | | Stop sound in object | The request is sent to the target object’s thread. Stop sound in object will stop the object’s associated sound. Associated sound is defined in the object’s sound tab. | | Stop this object | The request is sent to the Main Thread. This action will stop the event object from playing. This action is capable of stopping visible objects while displaying with show effects and can stop objects while moving along a path. When sound and video are stopped, then subsequent plays of the object will start the sound and video from the beginning. | | Stop this object’s sound | The request is sent to the event object’s thread. Stop this object’s sound will stop the event object’s associated sound. Associated sound is defined in the object’s sound tab. | | Thread FreeStyle script | The request is sent to the event object’s thread. This action will create a Script Thread for the target script and will start the script running separately in the new thread. | | Wait for advance | The request is sent to the event object’s thread This action provides a way to create a wait condition at any object. Using this action along with the Advance and Retreat, you can build slide shows quickly. Just checkmark the Shown event for each Image object in the slideshow script and use this Wait for advance action. Events, such as mouse button events can then be used to advance and retreat the slide show. | It is pretty clear that there are lots of rules to remember when dealing with threads (perhaps, perhaps not, let’s see). Even with all of this automatic Marshaling there is still a major problem. The problem is, what happens if you are using some object like an ActiveX control to place a Web Page inside of your project. Many complex ActiveX controls, like the Microsoft Web Browser Control, do NOT like to have their methods called from threads that are different from the one in which they were initialized. But to make Web Browsers work well, you do not want to run them in the Main Thread or Project Thread because they spend too much time waiting for the internet and that in turn can hang up your project. So consider this: you thread a Web Browser Control and then press some button in your project that calls a MediaBasic subroutine or a Scriptor Method that in turn calls a method in the Web Browser, like "navigate". Ouch! Unpredictable results. So how do you call a subroutine from some arbitrary event and have the subroutine run in the same thread as another object you intend to use. There is no way MediaForge can anticipate such a thing but it can provide a mechanism for dealing with this problem. The mechanism is "Async Event Handling." You will notice on the events tab that there is a check box for Async Event Handling. When this box is check, events are marshaled to the Project Thread. That’s easy to remember, but what about calling a subroutine that in turn calls methods in an ActiveX object like a Web Browser. Using Async Event handling you marshal the call to the Project Thread, but the Web Browser is running in its own thread, not the Project Thread. So now what? When you choose Async Event Handling on the Events tab and select an action that calls a subroutine or a scriptor method, then an Async Object field and finder button appears. You can specify the name of any object in the Async Object field and the subroutine or scriptor method will then be run in that object’s thread. So just put the name of the Web Browser in the Async Object field an any calls to subroutines that access the Web Browser Object. Thank you MediaForge. Now on to event handling itself. Once an action as been sent to the target thread, it should be apparent that something in that thread needs to process the action. MediaForge will process actions and other messages sent to the thread at strategic places while the thread is running. Essentially, the thread needs to be in a waiting state (ready to do something) before MediaForge will process requests sent to the thread. The most likely place for a thread to wait is at the end of a scene, and that is exactly what happens. When a scene has finished processing its objects, it then tells the engine to process any requests sent to it. The engine does more than just create a message loop looking for messages. In order to reduce the amount of processing performed, some code was added to the engine that actually puts the thread to sleep periodically. The thread wakes up if a message is sent to it. It’s kind of like having your cake and eating it too. Little things are infinitely the most important. Let’s look at a sample from the inside. Suppose a scene has a button and two sound objects. The sound objects have initial behaviors that have them wait to be played. The first sound is threaded and the second is not threaded. The button object has events checked that requests the first sound to play when it is pressed. The button plays the second sound when it is released. From our previous discussion about thread types we know that the button and the second sound exist in the Project Thread. The first sound exists in its own Object Thread. When the button is pressed, the request to play the first sound is marshaled to the first sound’s own thread. When the button is released, the play request for the second sound is handled directly by the Project Thread. The Object Thread is just waiting for some request to start playing the first sound, so it handles the Play request and the sound starts playing. The second sound starts playing because the scene is at the end and waiting for any requests sent to the Project Thread. Both sounds start playing at basically the same time unless you hold the button down for a while and then release it, in which case the first sound starts playing and the second sound starts playing the button is finally released. General notions are generally wrong - Mary Wortley Montagu In addition to the automatic waiting at the end of a scene, the following are other times a thread can handle Action requests: - When a Wait object is active. - When a threaded object is waiting to be played. - When a Button object with the "Wait until advance" option checked is active. - When the "Wait for advance" action has fired and the wait is active. - When an object has a "Delay" time that is active. The Delay is enforced just prior to object initialization, usually when an object is encountered in its script. - When a visible object is waiting to be removed as a result of the Remove after time Remove effect option on the effects tab. - When a Sprite object is waiting to animate its next frame. - When a Timer object is waiting to fire an elapsed event. - When a MediaBasic mfWait or MediaForge.Wait is active. - Periodically while MediaBasic code is running. - When the Launch object is waiting for an external application to complete. MediaForge is capable of handling Halt Project requests within the Stage Manager when image display effects are in progress or when an object is moving along a path. The Sound and Movie Objects handle limited Actions while they are playing: Halt Project, Stop, Stop sound in object, Stop this object, Stop this object’s sound, mfoSetFilename(extension), Hide, Hide this object, Show, Show this object, Enable, Disable, MCI Object commands. In our prior example, if we were to assign a path to the Button object, and press the button while the Button was moving along the path, the first sound (example event) would play as expected, but the second sound would not play until the button finished moving along the path. The reason is because the Project Thread is busy moving the button along the path and cannot handle the request to play the second sound until it finishes moving the Button. If you wanted both sounds to start playing when the Button was pressed while moving along the path, the solution in MediaForge would be to either thread the Button object, or thread the second sound. While on the subject of Scenes, it might be useful to examine the reason they were introduced into MediaForge. Why not just use objects and events? After all that’s how a dialog boxes work, just buttons and stuff firing events. It would be possible to drag objects onto the stage as separate dialogs or forms and thereby eliminate scripts altogether. All objects could be used as the result of events fired by other objects. Why introduce scripts, flow and scenes into an Object Oriented environment at all? At first, that was exactly the path the original developers, present company included, were on. There is no dishonor in changing your mind - An unknown Scotsman Why does MediaForge use Scenes and Backgrounds instead of traditional windows dialog boxes or Visual Basic like forms? The goal of Object Oriented programming in the first place was to help adapt artificial software to real world environments. If you look at a room of stationary objects, a form of objects pretty much handles the simulation. As the room gets more complex, that is as the objects start to move around, the form approach starts to get a little more difficult to justify. But the real problem is not with the complexity of the room, but rather the transition to another room. And of course, the real world, from the curious observer’s perspective, is not just a bunch of rooms, but rather new scenes that change gradually through semi-stationary backgrounds. Even this analysis is an over simplification but certainly more accurate than a world of forms. A form has no control over the sequence and timing of objects as they come into existence. Using unlimited background stacking with automatic scene transitions has turned out to be a very good approach and a valuable tool for MediaForge multimedia developers. Each new type of application just seems to work. Even when Consumer Authoring Technology was introduced into MediaForge for the purpose of creating end user Scrapbooking applications. The background and scene protocol in MediaForge makes the development of consumer products better and easier to develop. One event, such as a button press, can request a new scene. MediaForge handles the process of changing scenes. All the objects from the old scene and non-common backgrounds are removed. The engine process all objects in the new backgrounds and the new scene, and they are handled in the same sequence in which they are encountered. Images for example are displayed separately in the sequence in which they are encountered. Objects popping on the stage with their own unique effects and transition effects. That sound great right? Well it’s not really always the best solution. Sometimes the forms approach is better because with forms all the objects display concurrently, which may be the desired result. This is one reason MediaForge objects have initial behavior settings. Some of the initial behavior settings deal with scenes. For example, "When scene is ready, show this object." Put simply, scenes can easily be displayed like forms. What’s more, any object in the scene can be threaded, so it is possible to easily have objects displayed concurrently with concurrent transition effects. Try that with a form. When a scene is processed, any objects encountered with initial behaviors that have a "When scene is ready" option selected are queued. They are later shown or played when all the objects in the scene are initialized. The only thing that can interrupt the queuing of a scene’s objects is a Wait, such as a Wait object placed in the middle of a scene or a Button object with the Wait until advance option checked. When a Wait is encountered, the queue is aborted and all objects placed in the queue finish initializing and are then shown or played. After the Wait completes, for whatever reason, the queuing starts again with subsequent objects in the scene being queued. The transition between scenes is accomplished with the GoTo scene, GoTo next scene and GoTo prior scene. The GoTo Scene actions, triggered by events, will only work when the scene is completely loaded and waiting at the end of the scene or in the short interval between the initialization of objects in a scene. In contradiction to the previous discussion about actions being processed, when the thread is waiting, these special actions are NOT processed when a scene is waiting, unless it is waiting at the very end with all objects completely loaded. So the GoTo Scene actions are not processed when a Wait Object is active in the middle of a scene, or when an object is paused as the result of its Delay value and so on. If you have scenes with Waits or delays in the objects, and you have buttons in a common background that are used to page through the scene, then your project would be more user friendly if you Disabled the paging buttons when the scene is loading and Enable the buttons at the end of the scene. Otherwise, the buttons will be active and can be depressed, but they will do nothing until the scene is loaded. Earlier I mentioned that the MediaForge Stage Manager was capable of aborting a project even while an object was being displayed with a special effect. By default, projects can be aborted by holding down the Escape key. This sends an abort request to all threads. The Stage Manager checks for this request periodically while displaying objects with effects and when moving objects along paths. This abort key can be changed for any project using the Stage Attributes dialog box that is activated from the Stage pulldown menu on the Workbench. Choose the Style tab and you will see a place to change the abort key. The Stage Manager runs primarily in the Main Thread. Windows message handling routines make calls directly into the Stage Manager from within the Main Thread. For example, a request to paint some portion of the Stage is sent to the Stage Manager, which in turn will request objects within the area to render themselves. The Stage Manager is thread safe and objects running in various threads including the Project Thread are free to use the Stage Manager. So what exactly is this Stage Manager. Is it just some code that keeps track of objects and has them render themselves by displaying their contents? Actually the Stage Manager in MediaForge is very much like sprite managers in game applications. A great deal of effort went into the development of the MediaForge Stage Manager. The Stage Manager keeps a linked list of all active staged objects, and creates a stage sprite for each object, including an internal sprite used as the Backdrop. The Wings are the perimeter areas of the Stage Window that the Backdrop sits in the middle of. The sprites created are associated with objects and should not be confused with the Sprite object itself. When the Stage Manager needs to change a rectangle on the stage it requests that all sprites within the rectangle render themselves. The Stage Manager will even merge overlapping dirty rectangles. When stage sprites are asked to render themselves, they may call a render routine in their associated object or, if they own enough information, such as a bitmap for the object, which is the case with the Image object, they will render directly. The interesting thing is that they render to what is known as a back buffer. They never render directly to the Windows window or screen. The Stage Manager requests that the objects render themselves in sequence according to their depth. So the objects closest to the viewer are rendered last. The depth management is typically referred to as z-order in 3D jargon. Once all of the sprites have been rendered, the Stage Manager copies the changed rectangle from the back buffer to the corresponding area on the screen. The programming term for this is blitting. The sprite feature is available to all objects except those that must run in their own window. With some objects, like the Movie object, running in a window or as a sprite is optional. If the Movie object runs as a sprite, it can move and reside behind other objects and a transparent color range can be selected for blue screened movies. If you are developing visible MediaBasic objects, visible DLL Objects or windowless ActiveX objects, it is important to know that their mfoRender routine should be re-enterable because this routine can be called from the Main Thread as well as the thread that the object exists in, but not at the same time. When the visible characteristics of an object changes, the object can request a refresh from the Stage Manager. The Stage Manager will then render all sprites within the domain of the object. These sprites render directly or call render routines within each object. Objects will refresh for a variety of reasons including, moving, sizing, showing, hiding and animating. As an object moves along its path, the depth can vary and path events can be set at each node. Also, a MediaForge extension, mfoRefresh in MediaBasic and "Refresh" in the MediaForge itself are available to request a refresh of any object. Normally this extension is used to refresh the host object but it can be used to refresh any target object. A Refresh occurs immediately regardless of which thread the request was made from. A few words about DirectDraw are probably in order here. When DirectDraw Surfaces are selected on the Stage Attributes dialog playback tab, the back buffer described above is created as a DirectDraw surface. The Stage Manager provides a way for all objects to render to this surface by providing its sprites with both the DirectDraw surface as well as a GDI device context for the back buffer. As you can see, the Stage Manager is an important part of MediaForge that distinguishes it from standard authoring systems and programming languages, by providing a game like engine as an integral front end. The Stage Manager is used both at runtime and at design time At design time, a floating Stage Designer is available to help position and size objects, arrange, align and space objects, and define movement paths and create shapes. The Stage Designer works directly with the Stage Manager by sending information to stage sprites at the request of the user. Let’s briefly summarize what we know so far. MediaForge stores all of its objects in backgrounds, scenes, standbys and freestyle scripts. These are grouped with a background scene protocol instead of with dialogs or forms. A project tree view is maintained to provide a protocol overview for editing of the entire project. Objects and freestyles can run in their own threads. Scenes and backgrounds run in the Project Thread. Object Builders are used to modify object properties. Using Object Builders, actions can be requested for any events that an object might have. These actions will work safely with any target object or script, even if the target resides in a different thread than the event object. Objects are assigned their own sprites for participation on the Stage. The Stage is a three dimensional surface that provides all objects with the ability to reside at various depths and to move and animate. Now that we have a better understanding of actions and how they work safely within the MediaForge threaded environment, let’s take a look at the standard events that can fire action requests. Some objects, like the ActiveX object, have custom events programmed into the ActiveX control itself. There are standard events that are similar for all objects. An understanding of these standard events will be useful. Events are listed on the Event tab under the "On This Event:" category. Unlike Actions, the Events are not listed in alphabetical order. Instead they are grouped somewhat according to their behavior. The following table lists each event and tells when the event is fired. (Table 2) | EVENT | WHEN FIRED | | Setup | This event is fired when an object is encountered or first played. With visible objects, if the object is hidden or removed, then playing the object will fire this event again. With the Sound and Movie, this event will fire again if played after the sound or movie stops. This event fires before the object is Loaded, so you can use this event to Play a Calculation object to change the global variable used in the object’s Filename field. The filename can also be changed directly by calling a MediaBasic subroutine or DLL function to set the filename, using the mfoSetFilename extension. | | Loaded | This event is fired after the object’s data is loaded into virtual memory. For example a Sound object’s .wav file. If the object has a Delay time specified, the object will load the data while the object is waiting. Like the Setup event, this event will fire again if a visible object has been hidden or removed and then played. If a Sound or Movie has stopped and is played, then this event will fire again. Although this event fires in sync with the Setup event, this does not mean that the data is actually loaded again. If a Sound has stopped, for example, and the sound is played again, this event will fire. However, if the sound filename has not changed, then the sound file is not re-loaded. | | Start Playing | This event fires when the object is encountered or the scene is ready and the Initial Behavior authorizes playing the object. The event is also fired when the object is explicitly played. | | Going On-Stage | For this event to make sense, you need to remember that an object when shown and then later hidden, is still on the stage. Encountering the object with an initial behavior of show or play will put the object on stage. Playing the object after it is hidden or removed will put the object on the stage. So when an object is shown for the first time when encountered as the result of its initial behavior, then this event is fired just before it is shown. If the object is hidden and then shown, this event is not fired. If the object is hidden and then Played, this event is fired because it will be placed on the stage again using any show effects selected for the object. Removing the object, and then attempting to Show the object will result in the object not being shown and this event not firing because showing a removed object is insufficient to put it back on the stage. On the other hand, if an object is Removed and then Played, this event will fire just before the object is displayed. You will find that if a visible object has a show effect, this event is fired just before the object is displayed with the effect. | | On-Stage | See the description for Going On-Stage above because the same rules apply. The difference is that this event is fired after the object is placed on stage. | | Left Button Pressed | When the cursor resides over a non-transparent section of a visible object and the left mouse button is released, then this event is fired. The Button object handles this event a little differently than other visible objects. The Button object depresses and always fires this event regardless of the transparent setting. | | Left Button Released | When the cursor resides over a non-transparent section of a visible object and the left mouse button is release, then this event is fired. The Button object depresses and always fires this event regardless of the transparent setting. | | Right Button Pressed | When the cursor resides over a non-transparent section of a visible object and the right mouse button is pressed, then this event is fired. The Button object always fires this event regardless of the transparent setting. | | Right Button Released | When the cursor resides over a non-transparent section of a visible object and the right mouse button is released, then this event is fired. The Button object always fires this event regardless of the transparent setting. | | Middle Button Pressed | When the cursor resides over a non-transparent section of a visible object and the middle mouse button is pressed, then this event is fired. The Button object always fires this event regardless of the transparent setting. | | Middle Button Released | When the cursor resides over a non-transparent section of a visible object and the middle mouse button is released, then this event is fired. The Button object always fires this event regardless of the transparent setting. | | Cursor Entering Object | When the mouse cursor enters a visible object and encounters a non-transparent section of the object, then this event fires. The object’s selected cursor also changes at this time. | | Cursor Moving Within Object | While the mouse cursor is moving, if it resides over a non-transparent section of a visible object, it will periodically fire this event for the object. | | Cursor Leaving Object | When the mouse cursor leaves a non-transparent section of an object, then this event fires. The object’s selected cursor is replaced at this time as well. | | Dragging Within Object | While the mouse cursor is moving, with the left button pressed, if it resides over a non-transparent section of a visible object, it will periodically fire this event for the object. | | Off-Stage | When an object is removed explicitly using the Remove action, this event is fired for the object. This event does not fire when the object is simply hidden or when it is removed as the result of its script unloading. | | Enable | An object that has been explicitly disabled will fire this event when it is enabled with the Enable action. | | Disable | An object that is disabled with the Disable action will fire this event. | | Shown | When an object is displayed on the stage, as the result of being encountered, or when a hidden or removed object is shown or played, it will fire this event. | | Hidden | When a visible object is hidden it will fire this event. This event is not fired when the object is removed. | | Paused | This event is fired just prior to an object being paused. When an object is paused, its thread may be suspended. For example, pausing a Sprite object will result in the sprites thread being suspended. On the other hand pausing a Movie object or a Sound object will suspend the movie or sound but not the entire thread. This subtle difference is not very important because if a movie or sound is running in the same thread as other objects, no other objects will be playing. So pausing the movie or sound will not really effect the other objects. On the other hand if the movie or sound is threaded, then these objects are running in their own threads. | | Unpaused | When a paused object is unpaused with the Resume action, it fires this event. Being resumed a the result of "Resuming all paused objects" does not fire this event. | | Stop Playing | When an object finishes completely playing, or when an object is stopped with the Stop action, it fires this event. Is it just me, or does it seem that the correct term for this event should have been "Stopped Playing." | One other standard event is a path event. These events are optional and must be created with the Stage Designer. Each node on an object’s path can have its event added to the event list. As an object moves along its path, when it encounters a node that has been added to the event list, it will fire the event and perform the associated action. Creating a path event is a little tricky. While editing your project on the Stage, if you click on an object’s path node, the object will position itself at that node. You can then drag the object around in order to change the shape and length of the path. You can also create the path event at this point. Just select the Events tab on the Stage Designer and press the Add Path Event button. The Stage Designer automatically displays the object’s Object Builder with the Events tab selected. The next step is the tricky part. The path events are added to the bottom of the events list, so you must scroll down and find the new path event that has been created. You must select an action for the path event at this point or the path event will be removed when you exit the Object Builder. You can choose any action for the path event making this feature is quite powerful. This capability is made possible because of the way MediaForge marshals events from any event thread to any target thread. Actions in the target thread are processed by the thread while it is in a waiting state. There are various ways that MediaForge causes a thread to wait but all waiting objects and scenes use a fundamental routine deep inside MediaForge that processes a threads messages in phases. If you are a programmer with an understanding of the operating systems application programming interfaces then this information should be interesting. If you are not a programmer don’t concern yourself with this since MediaForge handles all message handling for you anyway. Thread message handling phases: Phase 1: Look for any message using the PeekMessage API command. If no message is available, then perform idle processing for this thread such as freeing unused virtual memory. Next shutdown the thread for a time using the MsgWaitForMultipleObjects command. This sleep time varies depending of the nature of the wait loop. If waiting for a specific amount of time to elapse as would be the case with the Timer object, then the thread sleeps for that amount, otherwise the thread sleeps for a maximum of 125 ms. While sleeping in phase 1, if a message is sent to the thread, then the thread wakes up and handles the message. Phase 2: Look for user interrupt hotkeys. These are the hotkeys created with the Hotspot Object and the project abort key. If a hotkey is processed, then restart the message handling with phase 1. Phase 3: Process Actions sent to this thread as the result of Events or the Script Flow object. Clear any parameters and data sent with the message and then return. Usually all actions are handled, but some wait conditions request that only limited actions be handled and that the rest should be dropped. For example, when the Sound object is playing it has its own wait loop that requests limited action handling. It does this because it may need to respond quickly to MCI or DirectSound; so MediaForge does not support playing some other object while sound is playing within the same thread. Phase 4: If the message is not an Action, then just pump the message through the operating system and return. Although the Main Thread uses a different message handling routine, both routines perform basically the same phases. In phase 3 though, all actions are always handled in the Main Thread. In Phase 3 actions are processed for the thread they were sent to. If you look closely at what happens when an object is played, the whole process is kind of interesting. First MediaForge marshals the request to the target object’s thread (see Table 1). Then the request is handled in phase 3 of the threads message handling routine. But what happens if the target object does not exist or it is destroyed between the time the action request is made and the target thread is ready to process the action. The answers are in the MediaForge target search and object validity handling. In MediaForge, if a target cannot be found, the request is ignored. This might seem like an oversight by the developers, but this behavior was intentional and actually takes advantage of the interpretative nature of the product. In reality, the request is not entirely ignored since a Missing Error is stored in the rwLastError reserved word each time an object search turns up empty. I will discuss the use of the Missing Error a little later on. Because more than one object can have the same name, one event object can target different objects at various times. Once the target object is found, a pointer to the object is included with the target request. When the thread is ready to process the request, the object pointer is checked for validity. For performance reasons, object validity does not perform another search, but uses some fancy code to verify the object instead. So if an object is destroyed between the time of the request and the time the thread is ready to handle the request, then the request is ignored. Also, if two objects have the same name, then the first object found by the search is the one used. The following is the sequence used to search for target objects: 1: Look at each object currently running in each active thread. 2: Look at each visible object added to the Stage Manager in the reverse order from which they were added. Visible objects are added to the Stage Manager when they are encountered or when they are first shown or played. 3. Look at each object In the Project Script. 4. Look at each object in the Project Standbys. 5. Look at each object in all currently loaded scripts in the sequence in which they were loaded. As you can see, objects are eventually located whether they have been initialized or not. Since MediaForge ignores action requests on target objects that it cannot find, you may want to create your own FreeStyle scripts for flagging this condition on specific objects. Instead of just playing an object, for example, you could Call a FreeStyle script that plays the object. The FreeStyle script then uses the Compare object to check the status of the rwLastError reserved word. If the result rwLastError is "Missing Error," then Show a hidden object in the FreeStyle script that indicates that the object was not found. Also, you can store object names in Global Variables and use the Global Variable name as the target for an action. This form of indirection is fully supported in MediaForge but getting to the cool way of using variables is almost a secret. If you right click on the target object name for any event or if you click in the Filename field in an Object Builder, or other various entry fields, you will notice a "Name Specifier" option on the pulldown menu. Select this option and a nice little name substitution dialog box will appear. You can use this dialog to find variables or even make other substitutions. These other substitutions include "Current Working Directory," "Registry Value," "Network Drive" and many others. This substitution dialog is definitely worth exploring. While on the subject of secrets, a nice trick to remember about the Calculation and Compare objects is that they can be played like any other object. I like to put Compare objects in the Standbys and prepare them with the Object Builder. Then I can Play them as the result of any event without having to call a FreeStyle script just to make one comparison. The Calculation object is more useful than you might suspect. There is no separate string or object name manipulation object available, unless you dive into MediaBasic. Instead, string handling was all placed in the Calculation object. There are many characteristics in MediaForge objects that are important to understand. The following discussion will cover what happens during the Initial Behavior and life cycle for each object. I will also attempt to get inside some of the more interesting aspects of each object. The objects are listed in the same order they are shown on the Object Palette. Release Object This is not an object! Press this button to simply de-select any selected object on the Object Palette. Stage Cursor Use this object to select a cursor for the entire stage. This cursor will take priority over cursors selected for individual objects unless the default priority of Object Cursor is used (see the Stage Cursors Object Builder). When this object is encountered or played, the Stage Cursor will change. It is easy to set the Stage Cursor for a specific scene by just placing this object in the scene. You can also place Stage Cursors in the Standby script and play them with events. There is no Events tab on this object so the Initial Behavior is not obvious. Inside, the default Initial Behavior is "When encountered, Show and Play this object." But unlike other objects with this initial behavior, the Stage Cursor will not abort queued objects in the scene and force them to display early. So it’s safe to use this object anywhere in the scene. Rectangle, Ellipse, Triangle, Polygon Objects These objects are similar in nature. They are all primitive graphics objects. The Rectangle object has support gradients and alph-blending and the Polygon object can be molded into almost any shape. To add lines and even spline edges to the Polygon object you need to place the object on the stage, and then use the Stage Designer’s shape tab. Once the Polygon edges have been added you can pull them around to various positions. These objects all have the same Events and behave initially like other visible objects when encountered and played. If you place any visible object in the Standbys, you must either Play or Initialize the object. Just requesting a Show for a visible object in the Standbys will do nothing since it is neither initialized or on the stage. Enabling a transparent color for these objects has no impact currently. Since the objects draw their own shape when the Stage Manager requests a render from the object, areas surrounding the object will not be effected. So in a way, these objects are implicitly transparent around the edges. Button The Button object is very versatile. This object has the same Events and will behave initially the same as other visible objects depending on the Initial Behavior selected. Enabling a transparent color with the Effects tab in the Object Builder will have no impact unless you have selected an image(s) for the button. The Button object has a special "Wait for advance" option on its General tab in the Object Builder. This option let’s you create wait buttons. I like to use Wait Buttons when debugging a script. It’s an old habit that is probably not as useful now that MediaForge 3.5 has the ability to set break points on any object from the right mouse click - quick menu list. It’s always a good idea to set cursors for button objects. Cursor feedback on buttons and other hotlink objects is very helpful to the end user and makes any project feel more like a multimedia application. The Button object has a Sound tab, so it is easy to associate a sound with any button. One of the quickest ways is to use the "Left Button Pressed" event and request a "Play this object’s sound" action. You can use the "Left Button Released" event to make normal use of the button.The Button object accepts images for its Normal, Over, Depressed, and Disabled states. Furthermore, Alpha-Channel images can be associated with each image state using a naming convention an checking the Alpha Masks options. See the Image Object for a description of Alpha Channel Masks. Image states can also use shared resources for their images. What this means is that several buttons can share images. The nice thing about this is that you don’t need to load multiple copies of an image into memory when the same images are used on buttons, even if there are lots of buttons. To choose a shared image, right click on the image field for any of the states and choose "Name Specifier" and then scroll down to "Loaded Object (for resource sharing)." This lets you choose the name of an Image Object instead of the filename for an image. This way multiple buttons can share the same image. One problem with shared resources is that you cannot see the actual image in the button and edit time, only while the project is running. You can trick MediaForge into showing the image at Edit Time though if you give the Image Object the same name as its associated filename. That way it knows where to look for the shared resource even though the Image Object is not loaded. Hotspot This object is normally superimposed over other objects in order to fire different events on various parts of an object. For example, you may have a picture and you want hotlinks on different parts of the picture. Simply use multiple Hotspot objects and position them on top of the picture. Although the Hotspots can appear translucent or opaque when positioning, they are always fully transparent when the project is run. Make sure that the Hotspot has a depth that is closer to the viewer than the object upon which you are superimposing.It is always a good idea to select a cursor for any Hotspot object using the Cursor tab on the Object Builder. A cursor is the only visible clue the user will have when moving the mouse over an invisible Hotspot. The Hotspot has all the standard Events and Initial Behavior options that a visible object has. I was trying to decide what the purpose of having paths and threading options for an invisible hotspot would be. I defined a cursor for a Hotspot and added a path and then made the Hotspot threaded. Sure enough it worked; the Hotspot moved along the path. The nice thing about a Hotspot is that you can mold it to any shape. As with the Polygon object, you can use the Stage Designers Shape tab to add line and spline edges that you can drag and size. Sound The Sound object in MediaForge is very robust. The default for sound is to use DirectSound with the ability to set the frequency, and run multiple sounds concurrently. If DirectSound has been disabled with the mfDisableDirectSound extension, then the sound file will be played with MCI. In prior versions of MediaForge, if DirectSound became disabled for some other reason, then MCI would be used as a backup. This approach has been dropped in order to avoid confusion. Starting with version 3.5, the first time sound is played, if DirectSound is not available, a message will be displayed indicating that DirectSound is not available and no sound will play. The Sound object has less Initial Behavior and Event options than visible objects but they work pretty much the same way. For example, "When scene is ready, play sound" is the default. If you want to only play sound as the result of some event, you need to be sure and change the Initial Behavior to "Pause sound until "play" is requested." By default, the Sound object will play threaded. This is probably the most useful way to play sounds since the sound object will wait in its own thread while the sound is playing. This is true with both DirectSound and MCI. As mentioned earlier, while playing sound and video, some event actions can be handled, but not all of them. In particular, the Play action will be ignored if it is marshaled to the same thread that the Sound is playing in, but the Stop action is supported. So if you want to start playing a sound again while it is currently playing, you will need to first stop the sound with the Stop action and then play it. The general sound tab, found on the sound’s Object Builder, is the same tab used with sounds that are associated with other objects. Associated sounds always wait to be played and do not fire their own events, so if you need more control and feedback, then you should use a separate sound object. The Sound object fires its Setup event when encountered or first played. It then fires its Loaded event after loading the file. Loading can occur during part or all of the Delay time for the object. The Start Playing event fires when the object actually starts playing. So if the Initial Behavior is set to have the object pause until Play, then the Setup and Loaded events will fire when the object is encountered, but the Start Playing event will not fire until the object is played. The Disabled event will fire each time the object is disabled. Attempts to play a disabled sound object will be ignored until the object Enabled. There is an Enabled event for the sound object as well. Finally regarding sound events, the Paused and Unpaused events will fire when the Pause and Resume actions are used on the object. If you pause a sound object and then resume it later, the sound will start playing again where it stopped. On the other hand, if you stop a sound object and play it again, the sound will start playing from the beginning. CD Audio This object is similar to the Sound object but the sound is played from sound tracks on the CD and no files are actually loaded into memory. The object can run threaded or non-threaded. MCI is used to play this object. The object is loaded by performing the necessary MCI commands needed to prepare the object to play the CD tracks. As with file audio, the CD Audio can be disabled, enabled, paused and resumed. Image This is used to show images of various types such as .BMP and .JPG. This is one of the most commonly used objects in MediaForge. As with most visible objects, visual transition effects can be set using the Object Builder. Image Processing is available for run-time smoothing, rotating etc. Initially the image will be displayed at its default size. You change the Image object’s size on the stage with the Stage Designer’s Position tab or by dragging the edges or corners. If the Proportional option is checked on the Stage Designer for the Image object that has been selected, then dragging the corners of the object will keep the object proportional. Dragging the side of the object will always bypass the proportional option. You can reset an Image object back to its original size by pressing the Reset button on the Stage Designer. A very nice feature, in my opinion, is the ability to move and size objects on the stage with the arrow keys and the shift-arrow keys; unless you enjoy threading needles with gloves on and moving things by 1 pixel with the mouse. When using .WMF metafiles with the Image Object, MediaForge performs some nice tasks inside for you. When a metafile’s size is changed, either while editing or running, MediaForge re-renders the object to its displayable bitmap. This keeps the metafile looking smooth regardless of its size, and it provides a bitmap version of the image for fast movement on the stage, for using the Show and Remove Effects, Image Processing options and transparent colors. A transparent color can be selected for any Image object, along with a range for the transparent color. One thing worth remembering is that in 16 bit color mode, often referred to as High Color, loss of color accuracy can occur. This is because the RBG color values are stored in less than 8 bits. In 24 bit and 32 bit display modes, often referred to as True Color, each red, green and blue value is stored in 8 bits. In 16 bit color mode, the colors are stored usually in the Windows default 555 format. That is, each color is stored in 5 bits. Another 16 bit color format used with DirectDraw surfaces is the 565 format. When a color is converted between True Color and High Color, the RGB values are rounded or lost because of the last two or three bits. This can cause problems with transparent color comparisons when running the same project in both High Color and True Color. If your project will be running in all three display modes, which is almost always the case, MediaForge has a nice solution for this problem. Just make sure that the transparent color has a range of at least 8 which covers the 111(binary) or 7 value variance for RGB colors between display modes. Did you catch the clue is the preceding sentence? The range value is the range for each for each red, green and blue value, not a range for the total color value. I could not find this documented anywhere in the MediaForge manuals. The color range for the relentless 8 bit palette display mode does apply to all 8 color bits though. I won’t trouble you with my opinion of palettes invented by a whiz kid at IBM back in the early days of the PC other than to say that drug usage must have been a factor. A good way to test your transparent color is with the associated cursor. When the mouse moves over an object with transparent colors, the cursor for the object is deselected when the transparent area is encountered. In a conversation, keep in mind that you’re more interested in what you have to say than anyone else is - Andy Rooney Another option worthy of discussion on the Image object’s Effects tab, is the Remove Options listbox. With this option, you can control the removal of the object prior to the object being destroyed or removed explicitly. The Remain At Z-Order is the default behavior and the object is only removed explicitly or when its script finishes. The Remove After Time is a quick way to remove an image after time without the extra effort of using an Action. The most interesting of these options, in my opinion, is the Move To Backdrop option because with this option you can improve the performance of your project. When this option is selected, the Image object is merged with the Backdrop’s internal object and then removed from the stage. So the image can remain visible, but there is one less object on the stage taking up memory and being handled by the Sprite Manager. Of course, only an impression of the object now remains, not the object itself, and it is behind every other object on the stage, so this should only be used where is makes sense. The performance improvement, though, of other images being moved and displayed over the image can be significant, especially if more than one image is merged to the backdrop. To clear images that have been merged to the Backdrop, use the "Clear The Stage" action. This action will also hide any other objects currently on the stage. You should review the Events listed in Table 2 because they all apply to the Image object. One event that needs some inside understanding is the Stop Playing event. When a visible object has no path, then the Start Playing and Stop Playing events are just fired quickly at some point after the Shown event. When an image has a path, the Start Playing event is fired when it starts moving along the path and the Stop Playing event is fired when it stops moving. There is an exception to this though. If you select a "Play this object" action for the Stop Playing event and the object has a path, the Stop Playing event will fire only when the object finishes moving along its path. The object will then immediately start moving along its path again. If the object is explicitly stopped with the "Stop" action, this event will not fire because there would be no way to actually stop the object. This feature was included to make it easy to create continuously looping visible objects. It is possible to use a Global Variable for the Filename in the Image object, by right clicking in the Filename field and using the Name Specifier option. If you change the value in the variable and want to re-display the image with the new filename, you should Remove the object and then Play the object. Just playing a visible sprite object will do nothing. Hidding and showing the object will not re-load the object. Hidding and playing the object will show the object with a show effect, but will not reload the object with the new filename. Alpha Channel Masks in MediaForge are a great feature. With the use of Masks you can create very professional user interfaces and graphics. Masks are a way to smooth the edges or shadows, and create semi-transparencies for an entire image on a pixel by pixel basis. In MediaForge, an Alpha Channel Mask is just a black and white image of any type. Colors in a Mask should only range from 0 to 255, where 0 is black and 255 is white. In RGB terms, the RGB values should all be the same, ranging from 0 to 255. For example: R=0, G=0, B=0 to R=255, G=255, B=255. A Mask can be a Bitmap of any color depth or even a JPEG file using this scheme. The Mask should be the same size as the associated image because MediaForge will do a (1 to 1) mapping of the images pixels and the masks pixels. When the mask has color value of 0 (black), then the associated pixel in the image is transparent. When the mask has a color value of 255 (white), the associated pixel in the image is opaque. Color values in the mask in between 0 and 255 result in a alpha-blending (semi see through) of the image with what ever is behind it. You specify an Alpha Channel Mask by clicking in the Alpha Channel Mask field on the General Tab of the Image Object’s Object Builder and pressing the Browse button. You also need to check "Use Mask" on the Effects Tab for the Alpha Channel Mask to take effect. Unlike 32bit file formats like .PNG that have fixed masks, you can change the mask for any Image Object at runtime with the use of the mfoSetMediaString1 extension or the MediaForge.SetMediaString1 method. Sprite MediaForge sprites are more advanced than most common sprites, like animated .GIF files. These sprites cycle through their frames in one place. More advanced sprites animate as they move, but the animation does not change pending the direction of the movement. MediaForge sprites on the other hand can have a separate .FSS animation sequence file for each of 8 directions plus another for Center Frame Cycling. When the Sprite object gets loaded, the .FSS files for each direction are preloaded. As the Sprite object moves on the stage, the sequence file defined for the direction in which the object is moving is used for the animation. As the Sprite object changes direction, it checks to see if a sequence file is defined for that direction. If there is a sequence file for the new direction then the animation is created by cycling the new directions file. The rate at which a moving Sprite object’s frames are cycled is determined by the granularity setting. The granularity simply tells the object that after it moves the specified pixels, then it should cycle to the next frame. Animation .FSS files are generated with the aid of the Sprite Import Wizard located on the Workbench Tools pulldown menu. Other animation files, such as animated .GIF , will be supported for each direction in version 4.0 of MediaForge. In place Center Frame Cycling, similar to animated .GIF files, is also available. With Center Frame Cycling, the frames per second option should be used to select the animation rate. Keep in mind that the Sprite object can have both movement animation and CFC animation. When the object stops moving, it will begin CFC animation immediately if the CFC Enabled option is checked. Among the options available for the Sprite, the Auto-return to center option can be confusing. Just remember that this option only has meaning if Center Frame Cycling is not used. In this case, the sprite will return to the first frame defined for the center frame, after the specified time elapses, and will not animate. If the Sprite object uses CFC animation, then the object is forced to run threaded. Any attempt to uncheck the Thread Object option will be ignored. The CFC animation technique changed with the introduction of MediaForge 3.5. Prior to version 3.5, MediaForge used a timer callback approach, a method found in some Windows 3.1 games. There are many performance and stability problems with the old timer approach that I will not bore you with, but the bottom line is that using threads is better and more consistent. CFC animation now simply cycles through the frames, waiting in between for the computed delay time. While waiting, actions requested from events fired in other threads are processed. Handling all actions while the Spriite was Center Frame Cycling was not possible in the earlier versions of MediaForge because there is only so much you can do in a timer callback routine. It is no coincidence that the timer callback was dropped when the threading option for all objects was included in version 3.5 of MediaForge. Most Sprites will need to run concurrently with other objects and object threading made the elimination of timer callbacks possible, but forcing threading on Sprites with CFC animation seems unnecessary to me. The reasoning was that CFC Sprites never end on their own so why not always make them threaded. That is an interesting argument worth thinking about but I predict that forcing threading for Sprite objects using CFC animation could be dropped in a future release, especially if someone decides that providing a time limit for CFC animations would be a nice feature. You should review the Events listed in Table 2 because they all apply to the Sprite object. As with the Image object, if you select a "Play this object" action for the Stop Playing event and the object has a path, the Stop Playing event will fire only when the object finishes moving along its path. The object will then immediately start moving along its path again. If the object is explicitly stopped with the "Stop" action, this event will not fire because there would be no way to actually stop the object. Also, the Sprite object will fire the Start Playing and Stop Playing events twice if it has both a path and performs Center Frame Cycling. When the Sprite finishes moving along a path, it fires the Stop Playing event just before it starts the CFC animation. The CFC animation fires the Start Playing event when it starts and the Stop Playing event when it finishes. The Sprite Object can also be use to create custom transition effects using an Alpha Channel Mask sequence. When you put the filename for an image in the "Sprite is Alpha Channel Animation for:" field the Sprite then becomes a transition effect instead of a traditional sprite. You can use Adobe Image Ready to create GIF black and white animations and import them directly into the Sprite Object using the "Import" button on the Object Builder. The GIF animation is then applied to the image on a pixel by pixel bases to alter the alpha blending for each pixel in the image. The net result is a custom Alpha Channel transition effect. See the Alpha Channel Transition sample project on the MediaForge web site for an example of this versatile feature. Movie The Movie object is provided for the purpose of playing video in your project. The video is played and controlled with MCI (Media Control Interface). The Movie object has one very cool feature not found in other video players. It has the capability to run the video through the Stage Manager as a sprite. This is the default setting for playing video. If you want to play the video in its own window, then check the Window (non-sprite) option found on the General tab of the Movie object’s Object Builder. Playing video through the Stage Manager as a sprite causes the object to render itself into the Stage Manager’s back buffer. This in turn makes it possible for the movie to have its own depth, move along paths while it is playing, and enables the transparency capability. Sprite videos can also have Show and Remove effects. These effects will transition at the beginning and end of the video for selected time periods. Sprite objects also support blue screen videos. Blue screen videos are videos with a common background color over multiple frames that can be used as a transparent color when played. Just select the transparent blue screen color on the Movie object’s Effects tab. You could have a video of a person, for example, walking and talking within other objects instead of just playing in a rectangular window sitting on top of the other objects. MediaForge can play any video file type so long as there is an installed MCI driver with a codec for playing the video through MCI. In order to play the movie object as a sprite, the driver must support the MCI_AVI_SETVIDEO_DRAW_PROCEDURE feature. This is essentially a callback procedure that makes it possible for the application to do its own bitmap rendering for each frame in the video. Video For Windows supports this feature for .AVI files. If the video draw callback feature is not provided in the driver, then MediaForge just defaults to playing the video in a window and you cannot uncheck the Window (non-sprite) option. The Movie object can play threaded or non-threaded but I would recommend using the threaded default setting in most cases because only limited Actions are supported in the thread in which Movie and Sound objects are playing. Playing Movie objects non-threaded would be useful if you want to play movies in sequence one after the other. When you create a movement path for the Movie object, the Delay time on the Stage Designer’s Path tab is ignored. Instead, the length of the movie and the spacing values on the path are used to compute how fast the object should move along the path. The speed is timed such that the object will finish moving along the path at the same time it finishes playing the movie. As a result, the Stop Playing event will only fire once each time the movie is stopped, even if the object has a movement path. One other quirk about the Movie Object is that it has a predefined "Play this object" Action for the "Left Button Pressed" event. Apparently, the reasoning was that visible but non-playing video will commonly start playing when you click on the movie. You should uncheck this event if you do not want the Movie to start playing each time it is clicked. Because Microsoft developers produced a conflict between MCI and DirectSound, the Movie object will disable DirectSound when it starts playing a movie. However, if DirectSound is already playing at the time the Movie object starts, then the movie will play silently. Data Entry The Data Entry object is provided to let the user enter data into a masked data entry field. So where does the data that is entered get put? It gets put in the Global Variable selected in the Variable section on the Data Entry’s General tab. This variable also provides the initial value for the object when it is first encountered or played. The data is transferred to the global variable when the Tab Key is pressed or when the mouse is used to click in a different Data Entry object. The data is not transferred to the variable when the Enter Key is pressed. The Data Entry object runs, not as a sprite, but as a separate child window on the stage. The object cannot run threaded or have a movement path, but it can run at any depth relative to other window objects. The interesting part about this object occurs when the object looses focus as the result of the Tab Key or the user selecting a different Data Entry object with the mouse. The data in the object’s entry field is then converted and transferred to the variable. Depending on the range values selected, two events may fire. A B. The A B event is fired when the value entered is greater than the high validation boundary value. The use of the validation values in the Object Builder is optional. If no validation is used, then no event will fire. Unfortunately, as of version 3.5 (build 4), there is no A = Ok event. This would be a nice event that could be used anytime the Data Entry object looses focus and updates the value into the variable. You might want to use this event just to reset the data entry field, or do some additional checking. That brings up a good question: how can you change the value in the Data Entry object with an Action? What I like to do, if I want to change the value in a variable, to say an initial value, is place a Calculation object in the Standbys. Then I use its Object Builder to make an assignment to the variable. Now I can just Play the Calculation object from any event to reset the variable value. Next, to reset the Data Entry object itself with the new variable value, I Play the Data Entry object. Anytime you Play a Data Entry object, the effect is to take what is in the associated variable and move it to the Data Entry objects input field. The Stop action does not seem to have much purpose with this object. The object never actually stops playing until its script finishes. If you want to prevent data from being entered into the data entry field, you should use the Disable action instead. The user can still move the mouse around in the field and even select characters, but they will not be able to enter new data or change existing data. Text The Text object runs as a sprite and has all the benefits therein. You can give it depth, assign a path, select special show and remove effects, and even use the Image Processing feature to smooth or anti-alias the text. The events fired are standard visible object events. The text field on the text object can be a fixed value or a Name Specifier, such as a Global Variable. The best way to use a Global Variable, is to right click on the Text field and select the Name Specifer item on the pulldown menu. When using a variable, if the value changes, you will need to Remove and Play the Text object in order to refresh its contents on the stage. Unlike the Data Entry object, just playing a visible sprite object, such as the Text or Image object will do nothing. The image must first be hidden or removed before being played. If you hide the object and then play it again, the text image will display with the chosen show effect. You might even get lucky and change the text in the object on the stage to the value in the variable. However, if you are using Image Processing on the text, or saving it as a bitmap, then an internal bitmap will have been created for the text object. The only way to replace the internal bitmap is to remove the object in order to force it to be created again when it is played. The bottom line is that if you want to be able to change the value in the text object on stage, regardless of which options have been selected, then you should Remove and Play the object after changing the value in the variable. This same principle applies to the Image object, since the filename for the image object can be a Global Variable. You will notice that there is no "Enabled" option in the "Transparent Color" area on the Effects Tab for the Text object. That is because a transparent color is always enabled for the text object. It is this transparent color that is used as the background when an internal bitmap is created for the text object. The transparent color is also used as the smoothing (anti-aliasing) and blur color. So if you want text to smooth over a background color, you should set the transparent color to a value that is close to the color behind the text. You can also use this to create interesting effects with text by having the transparent smoothing color be completely different than the background color. If you think about all of this you will realize that MediaForge will sometimes render the text to an internal bitmap on the fly. This makes special show effects and image processing on the Text object possible. But remember, a Text objects can also move along a path, and change size as it moves. This brings up an interesting question. If the Text object is sometimes rendered to a bitmap and processed, then how can it move quickly along a path while its size is changing? What happens is that MediaForge stretches and shrinks the internal bitmap as the Text object is moving and changing sizes. Then, when it reaches its final destination, if the size has changed, a new internal bitmap is created. This way the always text looks good, regardless of its size, when it is stationary. This same strategy is applied when .WMF metafiles are used in the Image object. Menu The Menu object is a nifty way to add pulldown menus to your project. These menus can function as visible sprite objects, with dept, show effects and paths. They can also attach themselves to the Stage like a standard pulldown menu. To choose which type of Menu you want, make a selection on the General tab of the Menu’s Object Builder. Sprite Menus only work with the mouse, while the Stage Menu works with both the mouse and the keyboard. When running as a sprite, the Menu can run threaded. For each item on the Menu, any action can be selected. This makes for a fairly powerful Menu. It is easy to get stuck building a Menu in the Menu’s Object Builder. Just remember that to submit text from the Item Text field, you press the Tab Key. For some reason, I keep trying to use the Enter Key. Use the mouse to select another menu item and then submit text for that item. The menu is created horizontally, until you click the Pop-up check box while positioned on a menu item. Then a pulldown menu starts building in the vertical direction. You can even have pop-ups on a pop-up menu item, which creates an arrow to the right with another pulldown menu starting at that point. As the Menu get larger it will sit on top of the Actions fields. You can drag the Menu Organizer out of the road to anywhere on the screen. When you select a menu item with the mouse you can choose any Action for the menu item. The event for that menu item is fired at runtime when the left mouse button is released over the menu item. With Stage menus, the Enter Key will also fire the event. Because of the way the Sprite Menu works, it is always a good idea to choose a cursor for the menu. Use the Object Builder’s Cursor tab for that purpose. Rich Text The Rich Text (RTF) object is used to include pre-formatted Rich Text into a Project. This object runs as a Window object, and as a result cannot have a path or show effects. The object cannot run threaded but the standard events for visible objects are available. If a Global Variable is used in the Filename field to identify the Rich Text file, you will need to first Remove and then Play the object to load a new file after the Filename variable is changed. Timer The Timer always runs threaded. There is no artificial limit to the number of Timer objects that can be active at the same time. The Timer has configurable events that can be used with any of the Actions. Because the Timer object is never visible, the Initial Behavior descriptions on the Events tab can be confusing. Starting with Version 3.5 Build 5, the Timer has only two Initial Behaviors. In prior versions, any of the five behaviors will always revert to one of the following: When encountered, Play this object, and When scene is ready, Play this object. The obvious way to use a Timer object is to place it in a script and let the timer start when the Timer is encountered or when the scene is ready. You can also place Timer objects in the Standbys and Play them from events. MediaForge 3.5 does a great job of marshaling actions from Timer events. Because of the Marshaling Manager, and the way the Timer runs in it’s own thread, all Actions are available to timer events. In Windows, the timer itself is a shared resource. If the Timer object used a Windows timer callback function, only limited Actions would be safe and even those could potentially freeze the entire operating system. Once the Timer dead event has fired - or the object has been explicitly stopped - the Timer object is available to be played again. Any attempt to play a Timer that is still running will be ignored. Wait The Wait object creates a straightforward sequential wait. There are no events or Initial Behavior. When the object is encountered, it waits for the requested time, requested mouse clicks, or requested key strokes, before processing the next object in the script. The Wait object is not playable, but there is a Wait action for creating waits from events. The Stop action will abort a waiting Wait object. While a thread is waiting, it can process all action requests except the GoTo Scene actions, unless the scene has finished initializing all of its objects.. Script Flow The Script Flow object is used to request actions, without using events. When the Script Flow object is encountered, the selected action is performed. At first, this object seems redundant, since actions can be requested from any event. In truth, this object is very useful. The classic example involves the complaint that MediaForge does not support multiple actions for one event. By using the Script Flow object and a FreeStyle script, multiple actions can be requested for a single event, and the sequence of the action requests is determined by the placement of the Script Flow objects in the script. Multiple actions, per event, are also available by linking events together. For example, an event fires that Plays an object. The target object has its own event such as "Start Playing," that event then fires and so on. The key here, though, is to remember that if you want to have multiple actions for a single event, just create a FreeStyle script and populate it with Script Flow (action) objects. Comparison This object is used to test the values of variables, and then fire events based on the results of those tests. For example, if the value in variable A is equal to the value in variable B, then fire an event. The nice thing about the Comparison object is that multiple events can fire for a single test. The Comparison object is playable. This means you can use it repeatedly by simply playing it. Putting Comparison objects in the Standbys is a great way to create comparison tests for single events. For example, if you press a button and you want to have a sound play if two variables are equal, just add a Comparision object for the two variables to the Standbys, and have a mouse down event on the button Play the Comparison object. The Comparison object can then Play the sound object if the two variables are equal. Jump The Jump object is used to alter the sequence in which scripts are processed and their objects are encountered. This object works with the Label object to jump to any location within the script or it can simply jump to the beginning or end of the Script. There is a Jump action available, as well, for jumping as the result of events. For the most part I would suggest not using the Jump object. It can be difficult to debug a script that is jumping all over the place. If you carefully think about what you want to accomplish, you should be able to get it done with events instead, which are easier to understand and manage. The one clever use for the Jump object though, is to jump around objects in a script in order to keep them from being initialized and thereby loaded until they are played. It’s a way of creating temporary standby objects, since they don’t get loaded until they are played. Later, unlike standbys, the objects are removed when the script finishes. Calculation This object is used to change variables, including string variables. Like the Comparison object, the Calculation object is playable. So this object can be used repeatedly, and even placed in the Standbys for changing values at the request of events. There are currently no events available for this object. Since the object is playable, I believe there should have been a "Stop Playing" event. For example: click a button, increment a value, and then have the calculation object do something else. This could be done other ways, of course, but it would have been convenient. Perhaps this event will be added to the Calculation object in a later version. Label This object is simply a place holder that should be inserted anywhere in a script for use with the Jump object. MediaBasic When you notice this object, you might wonder why it is necessary, since you can call MediaBasic subroutines from events. The MediaBasic object is used to create custom objects from MediaBasic code. If all you want to do is run some MediaBasic code, then call subroutines in the MediaBasic Project script. If you want build your own custom object, then you have come to the right place. The object can be either visible or non-visible and MediaBasic objects can run threaded or non-threaded. The first item of business, when creating a MediaBasic object is to enter a name into the Filename field on the Object Builder’s General tab. The name you enter into the Filename field must have a .ebs extension. Two files are eventually created, the .ebs(source code), and the .ebc(compiled code). The .ebs file is the file that will be Edited when you press the Editor button. The MediaBasic editor will compile this file and save the compiled code to a file with the same name but with the .ebc extension. If you do not give the file a .ebs extension, the object builder will attempt to edit an untitled file each time. I have no idea why the .ebs extension is not forced onto the filename since that’s the only file type used by the MediaBasic Object. If you do not know that the .ebs extension is required, then your just out of luck. I have already submitted my fix request for this problem. Let’s examine how the MediaBasic code associated with the object is used. MediaBasic objects can be either visible or non-visible depending on the "Make this a visible object" option. For the non-visible choice, when the object is encountered, with an Initial Behavior other than Hide until show or play, the main subroutine -Sub Main() in the basic code - is called. This code is then called every time the object is played with the Play action. That covers the non-visible choice, but for the visible choice, things are more complicated. The main subroutine is called whenever the object is put on the stage as the result of being encountered, shown or played the first time. When a visible object is encountered, regardless on its Initial Behavior, it is loaded and put on the stage. It might be put on the stage hidden, but it is still put on the stage. So when the MediaBasic object is put on the stage initially or explicitly-when played, the main subroutine is called. Hiding or removing the object and then playing the object will put it back on the stage and as a result will call the main subroutine. If you look at the description for the Image object you will see that the MediaBasic object works the same way. If you use show effects, the main subroutine is called just before the object is displayed with the show effects. Just showing a hidden object will not actually put it back on the stage, since it is already there. Hidding and showing a visible object does not display the object with effects. So the main subroutine is not called when the object is simply hidden and shown. If you think about what is happening here, it is kind of nice. The object’s main subroutine is called just before the object is put on the stage. This gives you a chance to perform whatever processing is needed to make the object look the way it should when it is rendered. The object’s Render routine is called by the Stage Manager when it needs to show the object. Clearly, the Stage Manager will not call the Render routine when the object is hidden or removed. So what happens next when the object is initially or explicitly Played? If the object has a path, then the Start Playing event is fired, the object moves along its path, and then the Stop Playing event fires. See the discussion on the Image object to see what happens if the Stop Playing event is used to Play the object again. The method used for continuously playing along a path is exactly the same as that employed for the Image object. Next, MediaForge tries to animate the MediaBasic object by calling its mfoAnimate subroutine. If the mfoAnimate routine exists, the Start Playing event is fired just before the subroutine is called, and the Stop Playing event is fired when it returns. Look at the MBSprite example that comes with MediaForge to see a visible MediaBasic object that moves along a path and then animates in place. Visible MediaBasic and DLL objects seem smarter than the Image object to me, because if they neither move along a path or animate, then the Start Playing and Stop Playing events never fire. I haven’t written much the about MediaBasic Extensions yet, but one Extension is worth mentioning here. Extensions are functions and commands that are essentially hooks into the MediaForge Authoring system. For example, you can Play any object directly from MediaBasic code by using the mfoPlay extension. Another extension, mfWait, should be used in the mfoAnimate routine between frames or render requests. This extension is able to wait for the requested time and also handle Actions sent to the thread while it is waiting. This way a Stop action, for example, can be sent to the MediaBasic object while it is animating. MCI This is a very general object. Movie objects and Sound objects use their own object name as the MCI alias when they are opened. This way the MCI object can target mciSendString requests to the movie or sounds by using their object names. The MCI object’s Command field, Alias field, and Argument list are combined to produce an mciSendString. When the MCI object is played initially or explicitly, the mciSendString is issued for the Movie or Sound object. If the Movie or Sound objects are running in a thread other than the thread in which the MCI object resides, the mciSendString itself is marshaled to the target Movie or Sound object. The special mciSendString is one of the limited requests handled by sounds and movies while they are playing. DLL The DLL object is used to create custom objects using Dynamic Link Libraries. This object is similar to the MediaBasic object, except that the DLL is written in some other language, such as C++. The object can be either visible or non-visible. The way the object handles events, plays the defined function and animates is almost identical to the MediaBasic object, so some of the following may seem redundant. If you have already read the description for the MediaBasic object, you may want to speed read parts of the description for the DLL object. I am providing it here for reference purposes. DLL objects can be visible or non-visible depending on the "Make this a visible object" option. They can run threaded or non-threaded. Let’s examine how the DLL code associated with the object is used. For the non-visible choice, when the object is encountered, with an Initial Behavior other than Hide until show or play, the function specified in the Function Name field on the Object Builder’s General tab is called. When this function is defined, parameters for the function should be included. If you are writing the DLL in C, supplying a .H header file will enable the Object Builder to extract the function parameters for you. For the visible choice things are more complicated. The selected function is called whenever the object is put on the stage as the result of being encountered, shown or played the first time. When a visible object is encountered, regardless on its Initial Behavior, it is loaded and put on the stage. It may be put on the stage hidden, but it is still put on the stage. So when the DLL object is put on the stage initially or explicitly-when played, the selected function is called. Hiding or removing the object and then playing the object will put it back on the stage and as a result will call the function. If you look at the description for the Image object you will see that the DLL object works the same way. If you use show effects, the main subroutine is called just before the object is displayed with the show effects. Just showing a hidden object will not actually put it back on the stage, since it is already there. Hiding and showing a visible object does not display the object with effects. So the function is not called when the object is simply hidden and shown. The object’s function is called just before the object is put on the stage. This gives you a chance to perform whatever processing is needed to make the object look the way it should when it is rendered. The object’s Render routine is called by the Stage Manager when it needs to show the object. Clearly, the Stage Manager will not call the Render routine when the object is hidden or removed. So what happens next, when the object is initially or explicitly Played? If the object has a path, then the Start Playing event is fired, the object moves along its path, and then the Stop Playing event fires. See the discussion on the Image object to see what happens if the Stop Playing event is used to Play the object again. The method used for continuously playing along a path is exactly the same as that employed for the Image object. Next MediaForge tries to animate the DLL object by calling its mfoAnimate function. If the mfoAnimate function exists, the Start Playing event is fired just before the function is called, and the Stop Playing event is fired when it returns. Look at the DLLDemo example that comes with MediaForge to see a visible DLL object that moves along a path and then animates in place. Extensions are - functions and commands - that are essentially hooks into the MediaForge Authoring system from within a DLL, OCX or MediaBasic. For example, you can play any object directly from MediaBasic code by using the mfoPlay extension. Another extension, mfWait, should be used in the mfoAnimate routine between frames or render requests. This extension is able to wait for the requested time and also handle Actions sent to the thread while it is waiting. This way a Stop action, for example, can be sent to the DLL object while it is animating. Now a word about calling conventions used in the DLL object. If I can clear up just this one issue, I will have done some good by writing this article. I found that the DLL object, and the DLLDemo used to demonstrate the object, use different calling conventions. That is not good! The problem exists only with calls from the DemoDLL into the MediaForge extensions. MediaForge is calling functions in the DLLDemo consistantly, using the Standard calling convention. By way of explanation, some enhancements to MediaForge, including the DLL object, were written at a different company. Most of those enhancements have been removed or re-written in MediaForge 3.5. Two programmers at Clearsand discovered this problem while building C++ templates for the new MediaForge 3.5 code generator. The calling convention has been changed and the problem is now fixed in MediaForge 3.5 build 5 and higher. The bottom line is this: DLL extensions in MediaForge 3.5 build 4 and lower use the normal C calling convention. DLL extensions in MediaForge 3.5 build 5 and higher use the Standard Windows API calling convention. So the DLLDemo sample and the Code Generators that ship with MediaForge 3.5 are both using the latest standard calling convention. The standard calling convention is available to other languages, such as Visual Basic. Calling C functions is difficult or even impossible in some languages, so changing the calling convention for the extensions was a good decision. If you do not have MediaForge 3.5 build 5, you should request it from tech support, or download it from www.clearsand.com. To check the version and build for your MediaForge, select "About MediaForge" from the main menu help pulldown. If you want to use the earlier versions of MediaForge with your DLL, then you need to make sure that the typedefs in your DLL use the default calling convention for C and C++ programs. For example: typedef bool (__cdecl *MFOPLAY)(LPCTSTR pName) Otherwise, use the standard calling convention for later versions of MediaForge. For example: typedef bool (__stdcall *MFOPLAY)(LPCTSTR pName) The following is a summary of the standard calling convention used in the Extensions for MediaForge 3.5 build 5 and higher. This is the same calling convention used by the Windows 32bit API functions. stdcall | Element | Implementation | | Argument-passing order | Right to left. | | Argument-passing convention | By value, unless a pointer or reference type is passed. | | Stack-maintenance responsibility | Called function pops its own arguments from the stack. | | Case-translation convention | None | Because, both calling conventions pass arguments right to left, this is a subtle problem. But don’t be fooled, your stack pointer is definitely getting messed up if you are using the wrong calling convention. If you are experiencing problems calling MediaForge extensions from your DLL, you should get the latest MediaForge, or change your DLL’s calling conventions. Launch The Launch object is used to launch external executable programs. There are three ways to Launch an external executable in MediaForge without resorting to MediaBasic or writing a DLL or an ActiveX control. The choices are: (1) Use this Launch object, (2) use the Launch external executable action, and (3) use the GoTo URL action. This object and the Launch external action both use the Windows CreateProcess, whereas, the GoTo URL uses the ShellExecute open, on the URL or file name. This object is forced to run non-threaded if the "Wait for completion" option is checked on the General tab in the Object Builder. The object is forced to run threaded if the wait for completion option is not checked. To be accurate, the object always waits for completion of the external program. It either waits in the scripts thread, or it waits in its own Object Thread, depending on the wait for completion option. The only Initial Behavior, currently supported by this object, is "When encounter, show and play.this object." Actions sent to the thread are processed while the object is waiting. The Launch object uses the external program’s process handle to check for termination. The "Start Playing" event fires after the external program starts playing. The "Stop Playing" event fires when the external program terminates. OCX This is the ActiveX Control object. With this object, MediaForge provides access to the large assortment of ActiveX controls that are available to all programming languages. So what is the difference between ActiveX controls and DLLs? Actually not much. ActiveX controls just provide a standard for calling functions, changing property values and dealing with events. This standard is called COM or Component Object Model. In reality, ActiveX controls based on COM and Automation are not that complicated. Most of the trade journals and manuals that deal with ActiveX controls are pretty scary, so I’ll try to keep my comments somewhat user friendly. MediaForge is a fully interpretive development system. The great news is that your projects never need to be compiled. So how does MediaForge call DLL functions and ActiveX methods without using a compiler? With the DLL object, MediaForge uses the GetProcAddress API to get a pointer to any function in the DLL. The parameters for the DLL Function are specified in the Object Builder, so MediaForge dynamically calls the function by pushing the parameters on the stack before making the call. But what about the ActiveX object? There is no place to specify a method in the Object Builder, so how do you call the Methods in the ActiveX control from within MediaForge? The answer is with MediaBasic. You call the methods from within MediaBasic using a MediaBasic Extension – mfoGetObject. This extension returns to MediaBasic an IDispatch pointer for any ActiveX object. A good example of this is the AMOVIE.MFP, the Active Movie demo that is included with MediaForge. Earlier I mentioned COM and Automation in the same sentence. You can read a hundred articles on COM and Automation without getting the simple answer to the question: What is the difference between COM and Automation? COM interfaces are just bundles of Methods(functions) available in the ActiveX control. Compiled languages need these COM interfaces to compile hard code access to the methods in the ActiveX control, just like they need header file descriptions of functions in a DLL. The technical term for using COM interfaces is early binding. Now to the MediaForge, late binding Automation way, of getting at the methods. All ActiveX objects have a common COM interface called IUnknown. The person who named this interface no longer has any friends. With this interface you can dynamically find all the methods in the ActiveX object using it’s QueryInterface method. Another common, but not universal, interface is the IDispatch interface. This contains methods for examining all the parameters in any of the controls methods and it even has a method for calling those methods on the fly. IDispatch is Automation! It is just a bundle of methods in the ActiveX control available for dynamically getting at and running all the other methods in the control. So COM is a hard description of all the Methods, Properties and Events in a control, and Automation is just a group of methods, in the control, that can be used to invoke any of the other methods dynamically. All controls have an IUnknown interface, and most support the IDispatch (Automation) interface. In order to use an ActiveX control in MediaForge, the control must support Automation. You can call any method and change any property in the ActiveX object by using the object returned from the mfoGetObject extension. Error handling is performed at runtime. If you call a method that does not exist, or if you provide the wrong parameters, you will get a runtime error. The following is a MediaBasic Subroutine sample that plays the ActiveMovie Control: Sub RunMovie() Set movie = mfoGetObject("mymovie") movie.run End Sub Currently, the ActiveX Control object cannot run threaded. This is because the MediaBasic code is not capable of marshalling object dot requests, such as movie.run. All mfo Extensions, on the other hand are marshaled just fine. The next version of the ActiveX control object will have an Invoke tab, where Methods and Properties can be prepared using literals or MediaForge variables for the parameters. A new Invoke action will pass through the Marshalling Manager, enabling the ActiveX Control to run in its own thread and still be controlled directly from MediaForge events. In addition, a new mfoInvoke extension will allow MediaBasic and DLLs to safely use the controls Methods and Properties. For now, the best place to put your MediaBasic subroutines that control ActiveX (ocx) objects, is in the MediaBasic script subroutine associated with the controls script. This will guarantee that the code that calls the controls methods will run in the Project Thread along with the control. Using the MediaBasic project script can be dangerous, because any call to a project subroutine is marshaled to the event objects thread, unless the event is in the Main Thread. Main thread events that call Project subroutines are sent to the Project Thread. So if a threaded object fires an event that calls a MediaBasic project subroutine, and the subroutine uses an ActiveX objects method, then unpredictable results will occur because of cross threading. If you know that the event is coming from either the Main Thread or the Project Thread, then using the MediaBasic project script will work. That covers Methods and Properties, but what about all the events available in a control. MediaForge lists all the events available in a control in the Object Builder’s Events tab along with the standard events. You can easily request any MediaForge action from any event fired by the control. The Marshaling Manager handles all the dirty details. You can even call MediaBasic Subroutines from ActiveX events and optionally pass the event parameters. The next release of MediaForge will include many new enhancements to the DLL and ActiveX object, including a new feature for writing generic MediaForge add-on objects. MediaPlayer The MediaPlayer object is a quick way to include the full power of the Microsoft MediaPlayer directly into you project. The MediaPlayer object is designed to always run threaded, which is very nice. In the Object Builder you choose how many, if any of the MediaPlayer controls should be displayed. You can obviously build your own controls and manipulate the MediaPlayer in MediaBasic or a Scriptor. Using the Remove action after a MediaPlayer Object is finished is the best way to end its thread. If you just hide the object when it is finished, you can play it again since its thread is still running. However, with the MediaPlayer object there can be sound as well as video so you will need to show the object again when you play it, otherwise only the sound will play. Playing a hidden MediaPlayer object does not automatically show it. Perhaps that's a design flaw, I'm not sure but it seems to make sense when you think about the duel audio video role of the MediaPlayer Object. Generic Window This object provides a way to create your own rendered windows in MediaForge. This is just a generic window that expects to be painted as the result of the Paint Event. The Paint event passes the window handle for the generic window to the event handler. For example: Sub DoPaint(hWnd as long) ‘ Do any rendering of the window here using the window handle End Sub You can obtain the window handle for any windowed object in MediaForge, including the Generic Window the help of the MediaForge.GetObjectWindowHandle method. This enables you to write into the window at any time, not just when the Paint event calls your subroutine or scriptor method. Some information on FreeStyle Scripts and Scenes When a threaded object is the ONLY object in a Threaded FreeStyle Script it will run NON-threaded. It's kind of an auto detect feature. The threaded object is running by itself, so instead of threading both the thread and the object, the object itself is not threaded. Otherwise trying to thread the script would just return immediately and nothing would happen. So putting a MediaPlayer object, for example, in a freestyle script and threading it is pretty much the same as just playing it, except that playing it is faster and more efficient. Calling the Freestyle script with just a theaded object(s) will return immediately because the object itself is threaded so the script has no reason to wait. When it returns the script is deleted along with its object. Threading a freestyle script with just one threaded object is the same as playing a threaded object, but less efficient. If some Scene can be used by more than one other scene you can save the scene that you are in using the mfGetCurrentScript extension. The following subroutine will save the current scene that called the subroutine and then goto the "Specify a location" scene. Sub GotoSpecify() mfSetString "LastScene", mfGetCurrentScript() mfGotScene "Specify a location" End Sub Then to return from the Subroutine you can use the variable with name substitution or call a return sub. So the cancel button in the "Specify a location" scene could say on left button pressed Goto Scene %VAR:LastScene% Still Confused about how event handling is Marshalled? In MediaForge 5.1 we've made it even easier. You can now direct MediaBasic Subroutine or Scripting Calls to any Object's thread. This pretty much solves the problem of controlling ActiveX controls from their own thread. On an event that needs to make calls the the ActiveX Objects methods, simply Select the ActiveX Object for Marshalling on the Events Tab. For example, a Button Object is clicked that changes the .SWF file of an ActiveX Object using Flash. In the Buttons Events Tab, you can force the MediaBasic Suboutine being called to run in the Flash Objects thread.
|