one.world – Tutorial Part I


The Counter Application

In this part of the tutorial, we are going to explore how to build a simple counter application in one.world. The counter application increments an internal counter once a second and displays its ID and the counter's current value in a window.

The counter application's window

This part of the tutorial introduces

and illustrates

Note that the one.world tutorial uses three browser windows: this window for the tutorial itself, another for source code, and another for class documentation.

You can find the entire source for this example in Counter.java.


Background

To build an application in one.world, you need to be aware of three core concepts.

  • Asynchronous events. Instead of synchronous method invocations, one.world uses asynchronous events, which are processed by event handlers and which are passed by value.

    "Asynchronous" means that when an event handler's handle() method has returned the requested operation may or may not have completed. "By value" means that after passing an event to an event handler's handle() method, the caller must not access the event anymore. We illustrate event handling in the event handling section of this tutorial part.

  • Components. Functionality in one.world is implemented in the form of components. Each component provides some service and interacts with other components through imported and exported event handlers. Components send events to imported event handlers and they receive events on exported event handlers. Components statically declare which event handlers they import and export in their constructors. They are dynamically linked with each other, typically when an application is loaded. We illustrate how to declare imported and exported event handlers in the main component section of this tutorial part. We discuss component linking and application loading in the initializer section of this part of the tutorial.

  • Environments. Applications in one.world run in environments. Environments can also store application data in the form of tuples (which are not covered in this part of the tutorial), and can contain other environments. In other words, environments are containers for application components, stored tuples, and other environments. You can think of them as a combination of processes and file system directories as found in traditional operating systems.

    Environments are also components. Application components interact with their environment by linking with it. We illustrate how applications interact with environments throughout the rest of this part of the tutorial.


The Main Component

When building an application in one.world, we first need to (a) decompose the application into components and (b) define the event handlers each component imports and exports. As part of this process, we need to define descriptors for each component and for each imported or exported event handler. These descriptors provide basic information about components and event handlers to one.world.


Our counter application is simple enough so that we only need to write a single component, which we call Counter. The component descriptor for Counter looks like this:

  private static final ComponentDescriptor SELF =
    new ComponentDescriptor("one.toys.Counter",
                            "The main counter application"
			    + " component",
                            true);
The component descriptor specifies the name of the component, a short description, and a flag for whether the component is concurrency-safe (that is, thread-safe). Our main component needs to be concurrency-safe, because it may have to process concurrent events.

Next, we define the exported and imported event handlers for our main component. Counter exports one event handler, which we call "main". When the counter application is loaded into an environment, this main exported event handler is linked to the environment's main imported event handler. The environment uses this event handler to notify the application of important events. Examples include notification that the environment has been activated, meaning that the application can start running, as well as notification that the environment is about to be terminated, meaning that the application must stop running. The exported event handler descriptor for the main exported event handler looks like this:

  private static final ExportedDescriptor MAIN =
    new ExportedDescriptor("main",
                           "The main exported event handler",
                           new Class[] { EnvironmentEvent.class,
                                         Timer.Event.class,
                                         DynamicTuple.class },
                           null,
                           false);
The descriptor specifies the name of the exported event handler, a short description, the types of events processed by this event handler, the types of exceptions raised by this event handler, and the linkForced flag used during component linking. We illustrate the use of environment events, timer events, and dynamic tuples in the event handling section of this part of the tutorial. In that same section, we also explain the meaning of the link forced flag.

When the user closes the counter application's window, the counter application needs to notify its environment that it has stopped. It can do this by sending an event to the request event handler exported by the environment. As a result, Counter needs to import a request event handler, to be linked to its environment's request exported event handler. The imported event handler descriptor for the request imported event handler looks like this:

  private static final ImportedDescriptor REQUEST =
    new ImportedDescriptor("request",
                           "The request imported event handler",
                           new Class[] { EnvironmentEvent.class },
                           null,
                           false,
                           true);
The descriptor specifies the name of the imported event handler, a short description, the types of events sent to this event handler, the types of exceptions sent to this event handler, and the linkForced and linkOne flags used during component linking. If the linkOne flag on an imported event handler descriptor is true, not more than one exported event handler can be linked to the corresponding imported event handler.

Our counter application needs to be notified every second that it should increment its counter. Fortunately, one.world includes a timer component to provide us with timed notifications. So, the main component needs to import a timer event handler, to be linked to the timer component's request exported event handler. The imported event handler descriptor for the timer imported event handler looks like this:

  private static final ImportedDescriptor TIMER =
    new ImportedDescriptor("timer",
                           "The timer imported event handler",
                           new Class[] { Timer.Event.class },
                           null,
                           false,
                           true);

Before we continue with coding the main component, let's step back and review the structure of our counter application. The counter application employs three components. First, its main component (the only one we need to write), provides the actual functionality. Second, a timer component provides timed notifications. Finally, like every application in one.world, the counter application interacts with its environment. The following diagram illustrates this structure:

The counter application's components
and their interactions

The arrows in this diagram represent the flow of events between imported and exported event handlers. The base of an arrow represents an imported event handler, to which events are sent, and the tip of an arrow represents an exported event handler, which accepts and processes sent events. Notice that event handlers have two names, depending on the view-point. Event handlers have one name in the component that imports them, that is, the component sending events. They have another name in the component that exports them, that is, the component processing sent events. The two names may be, but need not be, the same names.

Back to coding the main component. We have defined the descriptors for the main component's imported and exported event handlers. But, to use the event handlers, we need the actual event handlers, not their descriptors. So, let's define an inner class to actually implement the functionality of the main exported event handler:

  final class MainHandler extends AbstractHandler {
    protected boolean handle1(Event e) {

      // The event e is processed here.

      return false;
    }
  }
MainHandler extends AbstractHandler, because that class provides a reasonable default behavior for exceptional conditions, as well as utility functions to simplify responding to an event.

We also define the instance fields for the three event handlers imported and exported by Counter:

  private final EventHandler       main;
  private final Component.Importer timer;
  private final Component.Importer request;
We declare the two imported event handlers to be instances of Component.Importer and not to be just instances of EventHandler, because Component.Importer provides useful functionality beyond event handling, for example, the ability to test whether the imported event handler is linked.

Finally, we define the constructor for Counter. Besides initializing other instance fields, a component's constructor instantiates a component within an environment and turns event handler descriptors into event handlers. The constructor for our main component looks like this:

  public Counter(Environment env, String name) {
    super(env);

    // Initialize other instance fields.

    main      = declareExported(MAIN, new MainHandler());
    timer     = declareImported(TIMER);
    request   = declareImported(REQUEST);
  }
The constructor for every component must take an environment as an argument and must immediately invoke the superclass's constructor with that environment as its argument. This process instantiates the component within the environment. The constructor for our main component takes an additional argument for the counter application's name.

The declareImported() and declareExported() methods provided by Component turn event handler descriptors into event handlers. We simply call them with the appropriate descriptors and event handlers and assign the results to the corresponding instance fields.

You may already agree that writing out the basic structure of a component can get tedious real fast. You need to define descriptors, inner classes to implement exported event handlers, instance variables for event handlers, and so on. Luckily, one.world includes a handy utility, called Skeletor, to simplify this process. You simply write a short description of a component into a file, run Skeletor on that file, and it generates a skeleton source file for the component.

The description for our counter application's main component is in counter.skel and the resulting skeleton source file is in Counter.java.


Component State and Serialization

In this section, we are going to focus on how to implement a component's internal state and how to properly serialize it. one.world requires that all components be serializable, because one.world's support for check-pointing applications and for moving applications between nodes depends on it.

Before introducing all of Counter's instance fields and discussing how to serialize it, let's define three handy constants to represent the three states our main component can be in:

  private static final int INACTIVE   = 1;
  private static final int ACTIVATING = 2;
  private static final int ACTIVE     = 3;
In the inactive state, our counter application is neither counting nor displaying its window. In the activating state, our counter application is getting ready to count and currently scheduling timed notifications. Finally, in the active state, our counter application is counting and displaying the current count in its window.

Having defined the constants for Counter's three states, we now turn to its instance fields.

  private transient Object             lock;
The lock field provides the lock protecting accesses to all other fields. We need it because Counter is declared to be concurrency-safe. Generally, we prefer to define an internal, explicit lock instead of using synchronized methods, because other classes do not have access to an internal lock. Since lock is an Object, it cannot be serialized and thus needs to be declared transient. Declaring a field transient indicates to Java's serialization mechanism that the field should not be included in an object's serialized state.

  private           String             name;
The name field provides the counter application's name. It is assigned in the constructor as well as when the counter application is cloned.

  private           int                status;
The status field provides the current status of the main component, which must be one of the three constants defined above.

  private           long               count;
The count field provides the current counter value. We chose a long, so that the counter application can count for a very, very long time without rolling over.

  private final     EventHandler       main;
  private final     Component.Importer timer;
  private final     Component.Importer request;
The main, timer, and request fields provide Counter's imported and exported event handlers, which we already discussed in the last section. They are declared final because, just like name, they are assigned once in the constructor and never changed again.

  private           EventHandler       cancel;
The cancel field provides the event handler used to cancel timed notifications. Its value is provided by the timer component when it confirms a scheduled notification.

  private transient JFrame             frame;
  private transient JLabel             label;
The frame field provides a reference to the counter application's window and the label field provides a reference to the label showing the current count. Both fields are declared transient, because Swing GUI elements are not meant to be stored in serialized form. Yet, applications in one.world can be check-pointed, storing all their internal state in persistent storage.

When a new Java object is created, all instance fields are assigned well-defined default values, such as null. While these default values are just right for several of Counter's fields, lock, name and status require different initial values. So, let's complete Counter's constructor as follows:

  public Counter(Environment env, String name) {
    super(env);
    lock      = new Object();
    this.name = name;
    status    = INACTIVE;
    main      = declareExported(MAIN, new MainHandler());
    timer     = declareImported(TIMER);
    request   = declareImported(REQUEST);
  }
The newly presented code simply creates a new lock object, assigns the passed in name to the corresponding internal field, and initializes the main component's status to INACTIVE.

By deciding which fields to declare transient and which fields not to declare transient, we have already done most of the work needed for properly serializing and deserializing a component. The non-transient fields are automatically converted to bytes during serialization and back from bytes during deserialization by Java's serialization mechanism. However, we still need to worry about concurrency control during serialization and how to restore the transient fields during deserialization.

During serialization, Java's serialization mechanism reads an object's non-transient fields and converts them to bytes. While this is just the behavior we'd like to see, all field accesses, including those by Java's serialization mechanism, need to be protected by Counter's lock. We can achieve this by defining an explicit writeObject() method for Counter:

  private void writeObject(ObjectOutputStream out)
    throws IOException {

    synchronized (lock) {
      out.defaultWriteObject();
    }
  }
If an object defines such a method, Java's serialization mechanism calls the writeObject() method to serialize the object instead of accessing the object's non-transient fields directly. The above writeObject() method simply invokes the default serialization mechanism while holding Counter's lock.

When a Java object is deserialized, all transient fields are assigned their default values. To overwrite this behavior, we can define an explicit readObject() method. Our main component's readObject() method looks like this:

  private void readObject(ObjectInputStream in)
    throws IOException, ClassNotFoundException {

    in.defaultReadObject();
    lock = new Object();
  }
If an object defines such a method, Java's serialization mechanism calls the readObject() method to deserialize the object instead of restoring the non-transient fields directly. The above readObject() method simply invokes the default deserialization mechanism and then assigns the lock field a new object. Note that it does not restore the main component's frame and label fields. Restoring a running counter application's window is left to the event handling logic, because it can take a relatively long time and thus seriously slow down deserialization.

If you want to find out more about serialization, Sun's object serialization page includes the specification, which defines all the gory details of Java's serialization mechanism.


Event Handling

Asynchronous events are a core feature of one.world. They are used throughout the entire system and are the primary means for components to communicate with each other. In this section, we first show how to send events to imported event handlers and how to implement exported event handlers. We then peel back the cover and take a deeper look at one.world's event handling machinery.

To send an event to an imported event handler, we "simply" invoke the event handler's handle() method with an appropriate event. Consider, for example, the following source code snippet, taken from our main component's start() method.

    DynamicTuple dt = new DynamicTuple();
    dt.set("msg", "incr");

    timer.handle(new
      Timer.Event(main, null, Timer.SCHEDULE, Timer.FIXED_RATE,
                  SystemUtilities.currentTimeMillis() + 1000,
		  1000, main, dt));
This piece of code sends a timer event to the timer component, requesting timed notifications every second starting a second from now.

The first two arguments to Timer.Event's constructor are the source and closure of the new event. Every event must have at least a source and a closure, and the source and closure are customarily the first two arguments to an event's constructor. Responses in request/response interactions, such as the one started by above code snippet, as well as exceptional events, which indicate some exception relating to a sent event, are sent back to the source of the event. An event's closure is treated as an opaque token by the receiver of the event and is sent back with any response. In above piece of code, the source is the main event handler and the closure is null. The timer component will thus confirm the scheduled notification by sending the appropriate timer event with a null closure to the main event handler.

The next four arguments to Timer.Event's constructor specify that the new timer event (1) requests the scheduling (2) of fixed rate notifications, (3) starting a second from now, (4) to be sent every second. We use

SystemUtilities.currentTimeMillis()

instead of Java's

System.currentTimeMillis()

to determine the current time, because java.lang.System is among the Java platform classes that applications in one.world are not allowed to use. The full list of allowed and forbidden classes is documented in ProtectionDomain.

The last two arguments to Timer.Event's constructor specify that the actual notifications should be sent to the main event handler and that the event to be sent for a timed notification should be the dynamic tuple dt. Dynamic tuples implement a mapping from field names to values. The fields of a dynamic tuple can be dynamically added, modified and removed; field values can be of any type. Dynamic tuples are also events, making them a convenient choice for those events where defining an explicit class would be too cumbersome.

In the background section, we said that events are passed by value. one.world does not always enforce this, because it would introduce too much overhead into the system. Consequently, it is good programming style to create a new event when invoking an imported event handler and to not retain a reference to that event in the sending component, just as we did in the above piece of code.

To process sent events in an exported event handler, we typically define an inner class that extends AbstractHandler. We have already introduced the MainHandler inner class for our main component. Now, we fill in its event handling logic.

Our main event handler needs to process three types of events: (1) environment events sent by the counter application's environment, (2) timer events sent by the timer component to confirm the scheduling of timed notifications and (3) dynamic tuples sent by the timer component to notify the main component that a second has passed. We need to process environment events in our main event handler, because it is linked to the counter application's main imported event handler. We need to process the latter two types of events in our main event handler, because we used main both as the source of the event requesting timed notification and as the event handler to send notifications to.

Here is the code for processing environment events.

      if (e instanceof EnvironmentEvent) {
        EnvironmentEvent ee = (EnvironmentEvent)e;

        if (EnvironmentEvent.ACTIVATED == ee.type) {
          // Start the activation process.
          start();
          return true;

        } else if ((EnvironmentEvent.RESTORED == ee.type) ||
                   (EnvironmentEvent.MOVED    == ee.type)) {
          // Re-display window.
          show();
          return true;

        } else if (EnvironmentEvent.CLONED == ee.type) {
          // Reset name.
          name = getEnvironment().getId().toString();

          // Re-display window.
          show();
          return true;

        } else if (EnvironmentEvent.STOP == ee.type) {
          // Deactivate counter.
          stop();
          respond(e, new
            EnvironmentEvent(this, null,
	                     EnvironmentEvent.STOPPED,
                             getEnvironment().getId()));
          return true;
        }

      }

Activated environment events notify an application that its environment has been activated and it can start running. When receiving an activated environment event, an application typically starts the process of acquiring any resources it might need to get its job done. Our counter application, in start(), transitions from the inactive to the activating state and requests timed notifications from its timer component.

Restored, moved and cloned environment events notify an application that it has been restored from a stored check-point, moved to a different node, or cloned from another application, respectively. In all three cases, the application was running before the corresponding operation and has just been deserialized from its serialized state. When receiving a restored, moved, or cloned environment event, an application typically restores any resources, such as a GUI or access to tuple storage or a communication channel, that are not part of the application itself. Our counter application redisplays its window, which we did not restore during deserialization. When the counter application has been cloned, it also resets its name, since the name reflects the ID of its environment.

Stop environment events notify an application that its environment is about to be terminated and that the application must stop running. When receiving a stop environment event, an application should release all resources it has previously acquired and then respond with a stopped environment event. Our counter application, in stop(), transitions from the active to the inactive state by closing its window and canceling timed notifications. When our counter application has released all resources and ceased all activity, we notify its environment that, yes, we have stopped.

Keep in mind that every application must export an event handler, which is linked to its environments main imported event handler and which processes the above five types of environment events!

We have discussed how our counter application transitions from the inactive to the activating state and from the active to the inactive state. What about the transition from the activating to the active state? Our counter application makes this transition when timed notification have been successfully scheduled. Here is the corresponding piece of code, which processes timer events.

      if (e instanceof Timer.Event) {
        Timer.Event te = (Timer.Event)e;

        if (Timer.SCHEDULED == te.type) {
          // Display window.
          run(te.handler);
          return true;
        }
When receiving a scheduled timer event, our counter application has acquired its only resource, timed notifications. In run(), it transitions from the activating to the active state by displaying its window. Note that our counter application retains a reference to the event handler specified by the scheduled timer event in the cancel field, so that it can later use that event handler to cancel timed notifications.

The event handling logic of Counter is almost complete. All that's left is the processing of timed notifications.

      if (e instanceof DynamicTuple) {
        if (e.hasField("msg") && "incr".equals(e.get("msg"))) {
          // Increment count.
          incr();
          return true;
        }
      }
When receiving a dynamic tuple, whose msg field has incr as its value, our counter application knows that it is time: it increments its count and displays the latest value.

We are not going to review the implementation of the start(), run(), show(), incr(), and stop() methods of our main component. The source code is well documented and fairly straight-forward. However, there is one expression in the implementation of stop() that requires further explanation:

    Synchronous.invoke(c,
                       new Timer.Event(null, null, true),
                       Constants.SYNCHRONOUS_TIMEOUT);
When stop() returns, our counter application must have completely stopped, because we need to confirm to the environment that, yes, the application has stopped. As a consequence, just sending a timer event to cancel timed notifications is not sufficient. Rather, we need to wait until the cancel timer event has been confirmed. We use Synchronous.invoke() to perform such a synchronous invocation. invoke() calls the specified event handler with the specified event, setting the source of the event to some internal event handler. When the internal event handler has received an event or when the specified time-out has passed (which ever happens earlier), the synchronous invocation completes and returns the event or null, respectively. Our counter application, in either case, assumes that timed notifications have been canceled, returns from stop() and declares itself to have stopped.

Having discussed our counter application's event handling logic, it is now time to peel back the covers and take a closer look at one.world's event handling machinery. Let us assume that component A has an imported event handler, which is linked to an event handler exported by component B. Now, component A sends an event through its imported event handler to component B's exported event handler. How can we implement this event passing?

The simplest way is to use the current thread of execution and to just call the exported event handler with the sent event as its argument. By default, this is exactly what happens in one.world when one component sends an event to another component within the same environment (i.e., within the same application). The following diagram illustrates this straight-forward flow of events.

Simple event flow

The biggest advantage of the direct implementation of event passing is that it is simple and fast; after all, we are performing only a method call. However, it suffers from three problems.

The first problem with the direct implementation is that some event exchanges will break it. Consider, for example, two components that ping-pong events between them: Component A sends an event to component B, which responds with an event to component A, which responds with an event to component B, and so on. With the direct implementation, every sent event is implemented by a method call and in a ping-pong exchange none of these method calls return. Since every method call adds a new stack frame to the current thread's stack, the stack will eventually overflow, just like for any infinite recursion.

The second problem with the direct implementation is that, while event passing itself is fast, the component sending an event needs to wait for an indefinite amount of time before resuming its work. The component sending an event needs to wait, because the current thread is busy executing the code of the component that received the event. This is an issue, for example, for the implementation of the timer component used by our counter application. If the timer component used the direct implementation of event passing, its thread might be executing the code of a component that just received a timed notification, while it should really send another notification to some other component.

The third problem with the direct implementation is that it makes it really hard for us, the implementors of one.world, to isolate applications from each other and from the core system. If the same thread executes code from different applications and the core system alike, it is hard to activate and terminate applications independently and to protect the core system from misbehaving applications.

The solution to all three problems relies on a single mechanism, which we call an animator. An animator has a queue, storing pairs of event handlers and events, and a set of one or more threads. Each thread continuously processes the queue by removing an <event handler, event> pair from the head of the queue and by invoking the event handler on the event.

Using an animator, event passing can be implemented as follows. When component A sends an event to its imported event handler, the corresponding event handler exported by component B and the sent event are added to the tail of the animator's queue. Component A does not wait for component B to receive the event; rather, it immediately resumes its own work. Once the <event handler, event> pair has moved to the front of the queue, component B's event handler processes the sent event in its own thread. The following diagram illustrates the flow of events mediated by an animator.

Event flow through animator

Since event passing through animators uses separate threads that do not wait for each other, we call this style of transferring control "asynchronous events".

Now that we have looked at the two implementations of event passing, let's take a look at how the two implementations are used. In one.world, each environment has its own concurrency domain, which provides the animator for all components within that environment. Since the actual environments, that is, the code that performs operations on environments, are privileged, the environments themselves have their own concurrency domain, provided by the root environment (the "kernel" of one.world).

When linking an imported and an exported event handler, the linker determines whether to use the direct implementation of event passing or the animator-based implementation. If the two components to be linked are in different concurrency domains, all event passing is always mediated by an animator. The linker automatically inserts the appropriate code. For example, when linking our main component to its environment (we'll show how in the initializer section), the linker uses the animator-based implementation of event passing, because the main component and its environment are in different concurrency domains.

When linking two components that are in the same concurrency domain, the direct implementation of event passing is used by default. However, this default can be overridden by (1) setting the linkForced flag of an imported event handler descriptor to true, (2) setting the linkForced flag of an exported event handler descriptor to true, or (3) specifying true as the fourth argument to link(). For example, Timer uses the second option to ensure that all event exchanges with other components are always mediated by an animator and that its internal thread is not kidnapped by other components.

Asynchronous events are a good fit for applications that need to react to real world events, such as a meeting being started or a person leaving a room. However, asynchronous events can also make it harder to implement synchronous interactions, that is, interactions where we need to know that an event has been received by the intended receiver or that a requested operation has been completed. We already saw an example for a synchronous interaction in stop(), where we used Synchronous.invoke() to ensure that timed notifications have been canceled.

In fact, applications frequently need to synchronously schedule and cancel timed notifications. To make our lifes easier, the Timer component also exposes a synchronous interface. With the synchronous interface, you schedule timed notification with schedule() and cancel them again with cancel().

Furthermore, applications frequently need to time out request/response interactions and retry such an interaction if it fails. Instead of using Synchronous, which can lead to deadlock, you should use operations. An operation manages a request/response interaction by providing a timeout and retrying timed out requests. Furthermore, its implementation cannot lead to deadlock. The source code for one.radio.Main illustrates how to use operations.


The Initializer

Remember the diagram showing the structure of our counter application? Just to be safe, here it is again:

The counter application's components
and their interactions

In one.world, applications are loaded by so-called initializers. It is the initializer's job to instantiate the components of an application (the timer and main boxes in the diagram) and to link them with each other (the arrows in the diagram).

An initializer is implemented by a static method with signature:

  public static void init(Environment env, Object closure);
The environment passed to an initializer is the application's environment and the closure is an optional argument provided by the program loading the application. If an application is loaded from the root shell through the mk or load commands, the closure is an array of strings, just like for the main() method used to run regular Java programs.

An application's initializer may throw any Java throwable to signal that it could not properly initialize the application. In that case, the environment's imported event handlers are automatically unlinked, which effectively undoes all operations performed by the initializer.

The initializer for our counter application is implemented by Counter and looks like this:

  public static void init(Environment env, Object closure) {
    Counter comp  = new Counter(env, env.getId().toString());
    Timer   timer = new Timer(env);

    env.link("main", "main", comp);
    comp.link("timer", "request", timer);
    comp.link("request", "request", env);
  }
As already suggested above, Counter's initializer first instantiates a new main component and a new timer component. It then links the main component, the timer component, and the application's environment.

Since applications commonly use timers to detect timeouts for request/response interactions with asynchronous events, each environment has its own timer component. This timer component is accessible through the Component.getTimer() method. So, instead of creating a new timer component, we could also use this timer component:

    Timer   timer = comp.getTimer();


That's it. Go ahead and play with the counter application. It is part of one.world's source distribution and lives in the one.toys package.

If you are using the source distribution of one.world, you first need to build all Java class files. Consult the development setup page and the FAQ on how to setup your build environment and how to build one.world from source. You also need to change the startup scripts ("one.world.bat" for Windows and "one.world.sh" for Linux), so that the one.world.store.root property points to the directory you want to use for storing the persistent environment state. one.world recognizes many other configuration parameters, which are documented on the configuration page.

If you are using the binary distribution of one.world, you are ready to rumble. No building and no configuration are necessary.

To run one.world, execute "one.world" on Windows and "source one.world.sh" on Linux, which brings you into the root shell.

You can run the counter application from the root shell by executing:

  mk counter one.toys.Counter
  run counter
The first command creates a new environment named "counter" and runs the initializer defined by class "one.toys.Counter" in this environment. The initializer, in turn, instantiates the counter application. The second command activates the counter application.

If you want to see serialization in action, try check-pointing a running counter application. While the counter application's window is being displayed, execute

  checkpoint counter
and remember the displayed counter value. Now, exit one.world by executing
  exit
and then restart one.world again. You can restart the counter application from the saved check-point by executing
  restore counter -1
in the root shell, which will restore the counter application to the latest saved check-point.

Happy, happy, joy, joy!