Designing your Flip Application
Ohm Force has been using its Flip framework for years, to make, among other internal programs, its Ohm Studio application.
Over the years, we have developed a robust workflow to work with Flip. This chapter describes how you should organize your code in the first place, to avoid big refactors or headaches at a late stage of development.
This chapter will assume that your company is called ACME making Product and that all your code reside in namespace acme::product .
Code Layout
You should separate the model, client and server code in 3 separate folders, and 3 separate namespaces.
Typically you should create a folder named model in which all classes are in the namespace acme::product::model . The model is going to be used in both the client code and the server code. Since the server code is generally going to be running on linux, you want to avoid avoid dependencies between your model and code that wouldn’t compile on linux, and would be unnecessary for the server.
In the case that some code of the model would really need to reference other codes incompatible with the server, you should #define a macro ohm_flip_SERVER to evinct code not needed for the server. As we will see how to build the server, this method should be however used only when necessary.
Typically your folder client and server and their associated namespaces will have a dependency on model .
acme/ |
product/ |
model/ |
... model classes ... |
client/ |
... client classes ... |
server/ |
... server classes ... |
Converters
Your model folder should contain a conv folder. It will contains all your format version changes in a single place. Since Flip format conversion system is rather flexible, it is very easy to make document format changes, so you are very likely to make a lot of them.
To avoid polluting the model folder, having it separated in a different folder is a good solution.
You should name your conversion classes Converter#1_#2 where #1 would be the initial revision, according to your version source control naming. In the same fashion, #2 would be your destination revision.
For example, the Converter58186_58939 class would convert from the model revision 58156 in our source version control system ( svn ) to the model revision 58939.
acme/ |
product/ |
model/ |
conv/ |
... conversion classes ... |
Version Tree
Inside the conv folder, you should maintain a VersionTree class that will handle format version changes in a tree fashion.
You should name your document format version #product.Rev#revision . Where product would be your product name maybe prefixed with your company name, and where revision would be the version source control revision as described in the previous section.
For example a typical format version would be ohm.studio.58939 .
The format version is defined when declaring the root class and marking it as root, as for example in the following code :
ClassDescription <Root>::use ().enable_root ("ohm.studio.58939"); |
A typical version tree code would look as the following :
int VersionTree::do_convert (flip::ConvDocument & document) |
{ |
int err = 0; |
bool converted_flag = false; |
try |
{ |
if ((err == 0) && (document._format_version == "OhmStudio.Beta.Rev67847")) |
{ |
Converter67847_68679 converter; |
err = converter.process (document); |
converted_flag = true; |
} |
if ((err == 0) && (document._format_version == "OhmStudio.Beta.Rev68679")) |
{ |
Converter68679_69859 converter; |
err = converter.process (document); |
converted_flag = true; |
} |
if (converted_flag) |
{ |
document._rev_id += 1; |
} |
} |
catch (...) |
{ |
err = flip::Err_EXCEPTION; |
} |
return err; |
} |
Converter.txt
If you are working with many people on a Flip project, many people might modify the model simulatenously. This is not an issue when developping, as the server is bundled in the client, and because you can generally start on a fresh document to test your changes.
However when your application is running in a production environment, conversion will be need to be done, somehow like migrating a database. The model changes are therefore scattered over all the Flip classes which might be not easy when trying to gather all the changes to write a converter.
Therefore you should maintain a Converter.txt text file, typically in your conv where you will sum up all the changes needed for the next conversion, and maybe some directions on how to convert the document.
WARNING:Remember that the model is server code. The code contained in model is dead as seen by the server, but the code from conv is not.
Classes
You should name your classes like acme.product.ClassName where ClassName would be the name of your class.
For example a typical class name would be ohm.studio.Root . It is defined when declaring the class as for example in the following code :
ClassDescription <Root>::use ().set_name ("ohm.studio.Root"); |
Generally the model namespace is omitted as it would be redundant.
Enums
You should name your enums like acme.product.ClassName.EnumName where ClassName would be the name of the class containing the enum and EnumName the name of the enum itself.
For example a typical class name would be ohm.studio.ViewMetrics.Lod . The enum values themselves would be as in the source code, for example Lod_NORMAL .
It is defined when declaring the enum as for example in the following code :
class ViewMetrics |
{ |
public: |
enum Lod |
{ |
Lod_NORMAL = 0, |
Lod_DETAILED, |
}; |
}; |
EnumDescription <Lod>::use ().set_name ("ohm.studio.ViewMetrics.Lod"); |
EnumDescription <Lod>::use ().push_enum_desc (Lod_NORMAL, "Lod_NORMAL"); |
EnumDescription <Lod>::use ().push_enum_desc (Lod_DETAILED, "Lod_DETAILED"); |
Generally the model namespace is omitted as it would be redundant.
fnc.cpp
Finally your model should contain a fnc.cpp that will at least handle the declaration of the model. Typically it would look like :
void declare () |
{ |
flip::ClassDescManager::use_instance ().declare_basic_types (); |
ObjectRef::declare (); |
Selection::declare (); |
... code skipped ... |
Root::declare (); |
flip::EnumDescManager::use_instance ().post_check (); |
flip::ClassDescManager::use_instance ().post_check (); |
} |