Opensprings.org

Opensprings Core Module – version0.1 – Demo Release


Copyright (C) 2003 Hotsprings Inc.
For conditions of distribution and use, see copyright notice

Location: www.HotspringsInc.com

Description: As it's name says the core module is a collection of core components.
It defines the basic types available in the opensprings framework.
Although we commonly use STL across the code we decided to define our own String object instead of using the one provided by STL. The rationale for this decision is:
1) for most OS interaction one needs inplace conversion to C style ASCIIZ strings,
2) most of the string manipulation is greatly helped by copy on write policy
3) for MAC OS many OS calls require Pascal(255 char buffers) that we also wanted to have available with in place conversions.
4) the complexity of the String class is not such that a full rewrite would be a major waste of effort.

The assumptions and requirements applicable to std::string are generally the same as for XSP::String so it should not create too much trouble. Of course people using both our Strings and stl strings might find it annoying to convert from one to the other all the time. Tough luck :-)
StringMessage encapsulates a prerendered message object. The format specifier is a number ( the resopurce identifier of the format string inside the message table ).
The parameters to be placed withing the message are each already converted to String.
The message is formatted by the receiver in the language of his choice.
This approach makes StringMessage a lightweight component ideally suited for carrying the error messages inside exceptions. Methods are available to add new parameters.
The order of addition is important since the message parameters are identified by numbers. The format string is functionally equivalent to the format specifier in printf methods with a few enhancements to help out internationalization.
A string specifier is of the form: "blah blah blah &2; more blah blah &1; and &3;"
The parameters identified by the &ID; construct will be replaced by the string with the respective number in the parameter list of the StringMessage. How does this help ? The format string no longer relies on a certain order for the parameters and therefore it will not create problems to translations to languages where the word order is different.
Format specifiers addressing parameters that are missing from the StringMessage will be silently replaced with the empty string. Parameters in the StringMessage that are not used in the format string will be ignored.
Reference counting. We defined a set of interfaces and base classes to help with the definition of reference counted objects. Most objects should be defined and used based on this reference counting technique. If you don't want to worry about it too much just inherit your object from "ref_obj" and you are done. The "ref_obj" base class defines the addref, release methods, the counter itself and a virtual destructor.
By deriving from "ref_obj" you are gaining refcounting functionality without any extra effort. Please also remember to always make your destructor protected so you get an error every time you mistakenly use a refcounted object in a non dynamic scope.

To use objects that are refcounted we provide a templetized pointer object that will automatically handle the ownership of reference counted objects correctly. To use it just assign reference countable objects to it and act as if it were a plain pointer:
refc myPtr = new MyRefCountObj();
myPtr->MyMemberFunc(); A refc pointer is guaranteed to call addref on objects assigned to it and release on objects that it no longer owns. It is safe for assigning an object on top of itself. To clear a refc<> just assign zero to it. To check if it's zero ... yes, compare it with zero :-) The destructor of a refc object releases the associated refcounted object so you have guaranteed cleanup. Also during construction of compozite objects if an exception is thrown in the middle of construction, C++ knows as much as to clear the already constructed data members. So if they are refc<> objects you get proper cleanup in case a compozite failed to complete its constructor. For stack scoped objects an exception thrown somewhere inside the scope of the object is guaranteed by C++ to destruct the refc<> object, so again you are safe. A refc<> template has absolutely no extra weight compared to mannually calling addref and release correctly so, please, don't think of it as bloat and resist "optimizing" it out :-)
For the rare cases where refcounting is not desirable we also defined a ptr<> object to safely hold ownership of objects. It's destructor will delete the pointer. Assignement makes the source lose ownership in favor of the destination. Remember that 2 objects cannot "own" a regular pointer at the same time. Attempting to "know better" in one particular case or another will only get you in trouble. Other than that, ptr<>, behaves exacly as a pointer.

The memory management is rather light at the time of this writing, we definitely have to work some more on that. All we have defined is a set of new,delete operators that invoke the fastest allocator available on Win32 platform. A lot more is needed if we intend to get some help for memory leak detection. As time permits we will add such functionality.

The TimeMgr file contains a set of classes to handle the gregorian date time and the ability to convert time to OS specific representation. The classes are lightweight and use 64 bit numbers to cover both the finest resolution and large time frames.

The EventLoop is attempting to unify the various ways different OS-es deal with events, message queues and process scheduling. In our rendition to get something to run you should queue an agent object to the event loop. If you demand immediate execution your agent will be run as soon as it reaches the front of the queue. If you ask for it to be run later then it will be executed after the app had the chance to yield. This way you can queue both important jobs that require immediate execution and also tasks to be run when idle.
System messages like the ones windows sends to the Win32GUI are handled in bulk, but the algorithm we use will not allow that congestions on the system queue to prevent app agents from being scheduled and vice versa. Any agent (EventLoop::Event_Abstract) can be canceled and if so it will be removed from the event loop queue. If a canceled object is later queued for execution again, the position in the queue is NOT guaranteed to be the same as the old one. It is conceivable that an agent constantly canceled and scheduled never ends up beeing executed. Agents should be canceled on exceptional events triggered by the end user like "application close" or "stop download" or similar one time events. Please do not make the cancelation of agents part of their rutine existance like every time agent A executed it cancels agent B, then does something then schedules agent B again. Such a sequence will surely lead to agentB never beeing given a chance to execute at all. Think of it this way: "you should not be aware of the existence of any other agent but your own".

UnitTest: each module we develop has (or will have) a full set of unit tests to verify its correct behavior. When you write new objects please add unit tests for those too. If you ever find a bug or check if a specific use case may lead to failure please verify by writing and running a unit test. This way the tests that ensure correct functionality of the code will be available and ready to use every time we or you make any changes to the code base.

RuntimeVersion: a class that will hold the version info of the runtime environment your app is currently running on.

ErrorManagement: an exception object to be used to report unexpected errors. Our philososphy is that deriving C++ exceptions out of the base exception class is overkill. It would be really nasty to attempt to declare a new exception class for each place you throw from. And if you don't how are you to know what some specific code will need to catch ?! Besides in my experience when an exception is caught the catching code will either report the error or just do some cleanup and propagate it further independently of what caused the error in the first place. The model is quite simple actually ... most exceptions are related to missing resources or the system incapable of performing a specific task, most resolutions are about showing the error to the end user or jumping to some fallback mechanism. The client code does not need to care what went wrong, only that something did.
In modular environments where the catching code is written today to work on exceptions thrown by code written tomorrow it is unreasonable to expect knowldege about what kind of failure may be encountered. New classes of errors may exist tomorrow like "usb device not connected". How is a client who tries to save a file supposed to know that something called "usb" will be available one day and the agent doing the file write for him will fail because of it. How is an usb provider supposed to know what this particular module will find to be an acceptable exception type?! The situation is not solvable by means of typed exceptions. Types requires some sort of preagreement between the client (the catcher) and the server (the thrower). Exceptions just like events are not typed in their "heart" and attempting to design code that relies on types for such runtime concepts is a mistake in my opinion. All one can say with certainty at the time he writes the catch block is what to do if ANYTHING goes wrong. If you disagree and need to know what exactly the exception is about look at the message ID and at the parameter strings ... :-) Alternatively if you insist there's nothing preventing you from deriving your exception type out of "Exception" and catching the derived type where you need to. So we have one exception object, catch it with
catch(const Exception& err) { ... }
To make it easy to report errors where they might occur we created a compact, lightweight error creation mechanism.
Here's a sample:
Exception(XSPMSG(1,"Assertion failure in '$2;', while testing '$1;'"))
.Param(conditionStr)
.Param(locationStr)
.Raise();
As you see the exception is created on the stack, the message is plugged in, the parameters are added to it and the Raise method is invoked on the exception itself. The sequence is similar to the way C++ stream opjects use the << operator. For clarity we decided to give names to the functions instead of using some overloaded operator.

Note that the macro XSPMSG (one of the very very few macros within this codebase) is actually expanded to a number (the ID of the message) the message itself is ignored and collected separately by a script that places it together with the ID into the message table. So the Eception is created with a number and a bunch of string parameters. There is no bloating due to extensive error reporting so go ahead and use it as often as you need.

The ErrorSink is an interface needed to report caught errors. Certain modules in the application do not know anything about the environment in which they will be run but are forced to catch errors and report them anyway. So what they do is expose an refc, allow the programmer to set up that pointer to whatever he needs and then report errors to the sink. This way the programmer can customize the error reporting pipe and allow errors to be shown in GUI, transmitted over the network or whatever other requirement needs to be implemented. You are not stuck with modules reporting errors through some silly message box on the server when you want the client to see that error in his browser for example :-)




BACK
OpenSprings Website
Sourceforge Project Page