Main - Overview - Documentation - FAQ - Media

Operating Architecture

(How It Works)

This document explains how Vision works internally and why it was designed the way it is.  You do not necessarily need to read this document if you want to use Vision under the Application Library paradigm (described below) but it can shed some light on the motivations for certain decisions that were made in Vision's design.

The operation of Vision is divided up into two different kinds of actions: actions that occur in the server and actions that occur in the client.  The server holds all of the state for the GUI (graphical user interface) and has the responsibility of rendering it to the screen, communicating with the client side and loading resources on behalf of the client side.  Clients communicate with the server about their needs and perform specified application functionality.  As described below in the section Development Paradigms there are currently two ways to organize these parts.  The segregation of actions into those two distinct portions works well under both of them, however the Window Server/Client Program paradigm requires it and thus the motivation for the separation described below will be in terms of that paradigm.


Split Programming Design Philosophy

This particular design paradigm came about in response to two problems.  The first problem was that of allowing multiple processes to all contribute to one OpenGL context for collective rasterization, presentation and interaction.  Because an OpenGL context is local only to a single thread at a time means that only one thread of one process can hold the responsibility of actually managing the visual content.  Through means of IPC (interprocess communication), client programs wishing to use the window server to run OpenGL commands can send messages with such requests. However, this requires too much overhead and the latency of current IPC methods became the second problem because the latency is not sufficiently low to support realtime human interfaces in the way that Vision handles them.  The latency between the time that the event hits the window server to the time that the visual consequences of that event are observed was too great. 

The solution to these two problems was one that was initially counter-intuitive but proved to work remarkably well.  The solution was found to be the aforementioned separation of the portions of an application that deal with the user interface/presentation out from the main processing work of the program into separate processes.  All objects, data and code having to do with the user interface are stored in dynamic library modules which the window server loads into its virtual address space on behalf of client processes that wish to have that code executed or interact with it.  By itself, the window server upon bootup contains no code having to do with specific user interface constructs.  Thus a client process may request that the window server load any number of dynamic libraries (hereafter referred to as "server modules" and sometimes just "modules") on its behalf as well as make requests that the window server instantiate objects and data from those server modules.  Client processes can retrieve Objective-C object pointers of those objects instantiated in the window server and treat them just as if they were objects that exist their own virtual address space with the exception of direct member variable access.  The server modules that the client process requests can be those in repositories meant for general use, third-party libraries or completely custom server modules designed only for one client program. 

The general idea is that application developers write their programs in two portions.  One portion is the workhorse part that needs extensive CPU cycles, specialized timing, specialized hardware support, the security/space of a complete virtual address space or many other things that applications may require other than the user interface.  The other portion are server modules which contain all the code that makes up the constructs that users are going to interact with in order to use the program.  Ideally these server modules would also leverage code in other server modules (such as those in a repository) so that application development is as efficient as possible.  The design decisions behind what code goes into the server module and what code remains in the client process is very vital to the program's stability, security, speed and ease of development.  Ideally any data that the client process creates that has a direct and immediate impact on the visual interface should be passed up to the corresponding objects (instantiated from code in the companion server module) residing in the window server through normal Objective-C messaging.

Some programs may lend themselves to an entire existence in the window server without any need to communicate with the client process.  Most of the time these are smaller, more graphically oriented programs.  One example of this kind of a program is a Rubik's cube.  Because all code and data having to do with maintaining the state and interaction of a Rubik's cube directly and immediately impacts its presentation and interface, it is appropriate that it remain almost entirely within a server module.  An example of a fairly even code volume split would be a packet sniffer.  All of the networking code and callbacks remain in the client process.  The client process sends messages to its counterpart objects in the window server indicating that new packets have arrived and that they need visualization.  The objects on the server then react accordingly to the messages by creating other objects, changing values or other appropriate code.

This separation of code allows all the client programs access to the same OpenGL context which solves the first problem.  As for the second problem, because in most cases there is no messaging overhead (between event hit and visual reaction) that could be susceptible to the operating system scheduler and system load, the latency becomes negligible.  Once events hit the window server process, the consequences are evaluated and executed within the next run loop thus making the latency substantially lower.


Development Paradigms

There are two main ways to run Vision.  It can be run as a traditional API library that applications simply link to with all client and server activity occurring within the same process (this style of a traditional API library is the only configuration that Vision has been publicly released for at this time).  It can also be run as a window server/client program style system where functionality is segregated into different processes and there is interprocess communication. 

Window Server/Client Program

Application Library

As shown in the diagrams above there are two types of files that these portions act upon.  Server modules are dynamic libraries which hold code that is to be run within the memoryspace of the server in order to expand and extend functionality. Construct tree files are saved portions of the GUI state which can be built upon and used on behalf of client programs to save time instantiating Construct trees manually by communication with the server.


Window Server/Client Program
Application Library
Operating System Event To Visual Change Latency
Very Low
Very Low
Server/Client Communication Latency
Limited to IPC or Network
Almost Zero
Security Model Self Run Sandbox
Operating System
Multiple Clients
Yes
No
Operating System Window Server Capable Yes
No
Direct Memory Access To Server Objects
No
Yes
3rd Party Library Access In Server Process
No
Yes

The window server/client program paradigm (hereafter referred to as the "window server" paradigm) is used when Vision is being run as a window server process on an operating system (either the primary window server or one that runs alongside or within the primary window server).  The application library paradigm is used to create standalone programs that run within the primary window server.  The two different styles for developing and using Vision are meant to be as similar as possible to facilitate easy portability of programs between both paradigms.  The differences in the table note some places to be wary of.  Server/client communication latency can be an issue in the window server paradigm if messages are passed from the client side to objects that reside in the server too often, whearas in the application library paradigm there is no penalty from messaging at all.  Thus the segregation of function into server modules and client code becomes a very important design decision.  The security model of the window server is more restrictive than that of the application library and so low-level operations and features such as Objective-C categories which are allowed in the application library are not allowed in the window server.  Because direct memory access to server objects is banned in the window server paradigm all access from the client must happen through Objective-C messaging.  This restriction does not exist in the application library however.  An application library program may link to and take advantage of 3rd party libraries but server modules may not perform such linking and thus in turn the window server also may not.  The client program in the window server paradigm however may link to 3rd party libraries.


Role Of Server Modules And The Repository

Server modules are vital in the Vision operating architecture and their role is very important.  Great care should be taken towards their design.  The application library paradigm does not require that any code be loaded in at runtime and all relevant design code may included at compile time if so desired.  However, there is great power in keeping generally used design code outside in server modules.  Advantages include the leverage of publicly distributed server modules which would ideally be open source, robust and feature rich.  These modules would most likely provide commonly requested functionality thus alleviating the programmer from having to re-invent the wheel by creating code that has already been written, scrutinized and debugged.  It also encourages programmers to follow modular code design, reuse existing code and write more general/flexible code.

Server modules can reside anywhere as long as the code requesting it is aware of where it resides.  There is however a directory that is reserved as a repository for server modules which can be made available to all programs for use.  This directory is called the Repository and is located at /usr/local/Vision/Repository.  Application library programs at runtime by default change their repository path to MyApp.app/Contents/Resources/.

When modules are loaded from files and into the memoryspace of the server, their constructors and destructors ensure that the Objective-C runtime is properly prepared and that all necessary symbols are resolved before any code within the server module is executed.  Because multiple modules may be dependent on the same server module, instead of loading it multiple times it increments its reference count and keeps track of which clients are dependent on which server modules.  When the clients disconnect from the server all the resources that were requested and allocated on their behalf are released.  Only when a server module's reference count becomes zero is it actually moved out of the process space. 


Construct Tree Save Files

When Construct trees become complex it is very cumbersome to try and maintain their construction purely by managing the source code that instantiates it.  Thus there arises a need for storage and retrieval of Construct trees for reuse, clean coding policies and ease of creation/modification.  There are facilities in the base classes and in the server that allow for easy archival and retrieval of Construct trees.  There are also methods for more specific archival behavior through subclass message overriding.  When Construct trees are loaded from files they can be used in either a palette/template fashion where one construct tree is used as a prototype from with which to copy many other instances from or they can simply be loaded and put to work as they are.  There is an editor program called VisionEditor that specializes in visually manipulating construct trees and managing their archival into construct tree files.


Communication Between Server and Client

Because the server and client portions are present in the same process space in the application library paradigm, communication does not need any additional facilitation.  However, in the window server paradigm communication is carried out by language-level distributed objects.  It was decided to forego Apple's implementation of transparent distributed objects in favor of developing one specifically tailored to Vision because of certain restrictions placed on usage and bugs that are present in Apple's implementation. 

Communication in the split process situation is handled by transparent Objective-C messaging.  What this means is that even though the actual object that the client is attempting to message resides in the server process, it can still accomplish the message pass by instead sending the message to a proxy (and vice versa in the reverse direction).  As far as the client program is concerned the object that it is messaging is not a proxy but is for all messaging purposes, the actual target object (in the client's source code it is actually typed as such as well).  From its perspective it cannot tell what objects reside in its virtual address space and which ones reside in the server's.  When a program sends a message to an object that it thinks is of a specific type but is actually stored as a proxy what happens is described below:



If the receiving object is of the VProxy type then the Objective-C runtime will fail to find the method implementation for the selector and will forward it using the distributed objects functionality in the VProxy object.  This functionality occurs in two methods:

methodSignatureForSelector() is called to obtain the call stack structure (signature) for the method

  1. It first trys the explicitly assigned/cached signatures to see if it is there.
  2. Then it trys to see if the signature is within the current Objective-C runtime.
  3. If all else fails then it asks the receiving process for it.
forwardInvocation() is then called to actually forward the call to the real object
  1. It runs through all the arguments, push proxies, selectors and data for translation if necessary.
  2. It then creates the argument frame.
  3. Then it creates the IPC message and sends.
  4. It receives the return value (if any) and deals with data that has been pushed.
On the receiving end this is what happens:

requestCallback() gets the IPC message and services it accordingly.  If it is a method call then it follows these steps:
  1. It performs proxy and other translations as needed.
  2. It then uses objc_msgSendv to actually send the message to the real object.
  3. Finally it packages up the return value and sends it back to the calling process.

When proxies or actual objects are passed as parameters in messages that undergo this process they are translated into values that correspond to their counterparts in the other process.  Scalar parameter values however such as ints, floats, arrays and others are simply copied over.  If the corresponding counterpart object does not exist then it is created on the fly.  When the server and the client initially connect there is an object that is passed over which is then used by the client to initially send messages to the server and consequentially receive more remote objects which it can then send messages to.  The number of proxies in the processes then gets larger and larger as more messages are passed and more objects passed and returned.  Upon client disconnect all proxies and associated resources on both sides are released. 



Copyright © 2004-2011 Aoren LLC All rights reserved.
[email protected]