ENGINE-3 (aka M3 Engine; previously M3, or MDI 3) FEATURE LIST - ObjectRegistry (_application_scope; ScopeService) - MessageService - TransactionService - Notes on Signals/Slots (impl w/ Wires) - Primitive Wires and Wires - Tasks - Engine OBJECTREGISTRY (previously _application_scope; ScopeService) The ObjectRegistry (OR) is a central runtime repository for persistent object instances. Features of the OR: - reduce global namespace pollution - increase discoverability (of other registered persistent instances) - enforce standardized API The OR requires an object to implement the folloing properties: - object_id = UID - object_name = friendly name - object_version = class version - ready_state = 0 - 5: - 0 = uninit - 1 = init - 4 = ready - 5 = unloading/destroyed - (optional) sigterm = termination signal. Though a public destroy message is sent for UI objects, core objects should use term signal to prepare for destruction. EG: Message service uses this to stop sending delayed messages. MESSAGESERVICE A message is a one-way communication. The MessageService supports: - SendMessage: sends a message through the message service which is published under the send_message subscription. - Subscribe: subscribes to a message with a receiver/handler/callback. - Publish: sends a message to the message service for a particular subscription, and that is delivered to all subscribers. (FIXED) Note: need to update how subscriptions are stored and published. At the moment, it iterates through everything and it should only do so for particular messages. TRANSACTIONSERVICE A transaction is a two-way communication. While it works with the MessageService, it should not be confused with the MessageService. Where the MessageService is one-way, the TransactionService is two-way. An object must support the following API to register with the transaction service. - Same as OR, AND - boolean startTransaction(TransactionService service,TransactionPacket packet) - boolean doTransaction(TransactionService service,TransactionPacket packet) - void endTransaction(TransactionService service,TransactionPacket packet) The TransactionService supports: - [TS].register([obj]): registers an object with the service. Objects registered with the OR may automatically be registered if the OR is configured to do so. - [TS].openTransaction: opens a new transaction. If a transaction is already open, then it is left alone. When opened, the startTransaction method will be invoked for every object registered with the transaction service. If a controller object is specified, that object id will be associated as the owner for the new transaction packet. - [TS].serveTransaction([TransactionPacket]): Sends the TransactionPacket to any objects listening for the transaction. Packets are first sent to the controller, if one exists. XXX - [TS].startTransaction: invokes startTransaction on registered objects for a given packet. If the return value is true, the object will continue to participant in the transaction. If the return value is false, the object will not participate. The controller can also block the start signal on the packet to prevent propogation to all other registered objects. A controller can also add objects as participants for a packet if that object is registered. XXX - [TS].close: closes a transaction. Regardless of whether a transaction is completed, closing a transaction will end it. XXX - [client].doTransaction([TransactionService],[TransactionName],[TransactionPacket]): Registered objects have the transaction packet pushed to them. XXX - [TS].getTransaction([PacketID]): returns a TransactionPacket from an ID. XXX - [TS].updateTransaction([TransactionPacket]): records an update to the transaction packet. If the status is final or closed, then the appropriate method is invoked. XXX - [TS].cycleTransaction([PacketID]): invokes serveTransaction for a TransactionPacket with id PacketID. XXX - protected [TS].finalizeTransaction([TransactionPacket]): If the packet is closed, then the transaction is closed and all open pointers for this transaction are cleaned up. The client.doTransaction is responsible for reading the TransactionPacket object, verifying that the object can do something with the packet, perform some action, update the packet status as needed, and return a boolean value. Clients that do not return boolean values should be nixed automatically. Clients that do not implement the correct API will by summarily denied registration. A client needs to implement startTransaction, endTransaction, and doTransaction before it registers or opens a transaction. Example: Tasks with dependencies use transactions to monitor the state of each dependency. Note: This task example equates to a primitive wire using transactions. The transaction has no responsibilities whatsoever besides waiting for the dependencies to be loaded. For example: - Task demo_task is of type transaction. - Task demo_task defines a handler that is of type message. - Task defines a primitive wire for the transaction and the handler. When the PW is created, if the src for the transaction is "self", the object_id of the TaskService is used. - Task demo_task has one dependency, "_wait_for_this". - When demo_task is handled, the TaskService invokes its PrimitiveWire. The PW opens a transaction to the src, in this case the TaskService (see above above remark about "self"), and serves the task back to the TaskService via the TransactionService. - The Task checks to see if it has any dependencies. If not, it finalizes the packet - which, coming from the PrimitiveWire, holds no data - and returns. The TransactionService will notify the caller, in this case, the PrimitiveWire, that the transaction is finished. - The PW invokes the handler when the TransactionPacket is complete. In the above example, a basic task wraps some involved communication. Is all of this necessary? No. The use of PrimitiveWires and TransactionServices could have been left out. However, that makes the TaskService more complicated and conditional. The original TaskService was so, and its limitations led to the design of the TransactionService and Wires. Note: The PW must be registered with the Transaction Service. SIGNALS AND SLOTS A signal and slots implementation can be based on Wires and PrimitiveWires. At present, the S&S implementation via MS requires objects to subscribe to particular messages, and source objects to publish messages. WIRES AND PRIMITIVE WIRES A wire is similar to the Signal and Slot paradigm, but IS NOT THE SAME. In the signal/slot structure, an object signal is connected to one or more object slots. When a signal is emitted from an object, the appropriate slots are invoked. In the wire structure, an action and handler are joined together by a reference. The action object does not need to emit, and can operate independently of the wire and the handler. A primitive wire (PW) connects a script function, transaction, procedure (xml-based javascript statement), or a task with a handler. the handler is only fired if: the function returns true, the transaction is completed, or the [WIRE].fireWire method is invoked. A primitive wire: - can only be used once, and is destroyed after its handler is invoked. - can register with the TransactionService - is not registered with the ScopeService. - is represented by a WireID, not an instance. Wires will be instances. Primitive Wires are not. A wire: - is a PM with more capabilities. - is an object instance. - can be intercepted and joined. The design of a non-primitive wire is in process. TASKS A task: - is identified by a unique id. - Uses primitive wires for actions/handlers. - Uses transactions for dependencies - this needs work because it should be automatic. The crux of the problem is that a task with dependencies and an action that is not a transaction must somehow indicate that its handler is not to be invoked until all of its dependencies are finished. Alernately, this could be done by rigging PWs to include a transactional option, or by using the more advanced Wire instance with a interceptor. At the moment, it makes sense that a task with dependencies, by default, is transactional, and either its default action becomes a dependency, or an additional dependency is added that is transactional. However, that still means the initial action must be intercepted. It makes sense, then, that an initital action that is not a transaction be pushed down as a dependency and the primary action becomes a transaction. - Can include dependencies - aliases a function, a procedure, or a task. - aliases a handler that is a function, a procedure, or a task. Example: The above example represents the problem case. It's not really a problem, just a kludgy implementation. A task defines an action that is not a transaction, but it has dependencies. So, the task must intercept the function and the handler. Currently, this happens by executing the action, then checking to see if there are any dependencies. However, this is not favorable because there is no connection between the action being executed, the handler being executed, and the dependencies being completed apart from the order. The process works, but there were many cases where it is desirable to have the stronger tie. Therefore, the default task action should be moved to be a dependency if one or more dependencies exist, and then the default action should be replaced by a transaction. The prototype of this would look like the following. Note that a developer would not actually specify this, but it is instead what the code would do with the above example. ENGINE A web application engine instance is a framework for web applications. The engine instance framework is: - uses a TaskService to perform zero or more tasks that may create and/or serve to manage the instance, or create subordinate object instances. - uses a MessageService to publish and send messages. - uses a ScopeService (_application_scope) to manage object instances. Engine Implementation The primitive instance (PI) is a single element, engine, with no attributes. Example: Primary Engines There can be only one primary engine (PE). A PE is responsible for publishing the application_initialized message. The PE is either the first engine, or the first engine with a primary attribute. If the attribute is specified on more that one engine, subsequent engines are not treated as a primary. An engine PI will: - execute a task to load the engine3_tasks.xml, and whose handler will try to execute a default task defined in the XML file. - default task adds task dependencies to: - create new ConfigUtil that loads engine3_config.xml - depending on the configuration: - executes tasks to intitalize components for UI widgets, eg: StyleManager. - add a dependeny for the window.onload event. This particular event is handled internally by the TaskManager instance. - publish engine_load message - if this is the designated primary engine, publish application_initialized message