Flip Basics Programming Guide

Controlling the Model

Now that we can declare a model and we know how to organize it, this chapter describes how to manipulate it.

This chapter will use the model declared in the chapter Declaring the Model.

The Document

To manipulate the model, one must have an instance of the model which is called a document.

A Document is therefore described by the model it uses, a unique user 64-bit identifier, a manufacturer/product identifier as well as a component identifier. The latters only allows to know who is the originator of a modification and allows for concurrent modification of a document.

#include "flip/DataModel.h"
#include "flip/Document.h"
class Model : public flip::DataModel <Model> {};
...code skipped...
void  run ()
{
   declare ();                                                       (1)
   Document document (Model::use (), 123456789ULL, 'acme', 'gui ');  (2)
}
  1. You must first declare your model before you use it
  2. This line will create a Document which uses Model, 123456789ULL user unique identifier, 'acme' manufacturer/product for the 'gui ' component

How it Works

When you change the document, Flip will know that it was modified. Transactions represent the difference between two document states. When the document is modified, Flip internally keeps the old and new state of the document.

When you have done the necessary changes that represent a logical modification of the document, you can then commit the document to apply those changes.

In that sense, a transaction (or modification of the document) is always made so that the new document is valid from the model point of view.

Modifying a Property

For this example, let's suppose that we want to change the tempo.

#include "flip/DataModel.h"
#include "flip/Document.h"
class Model : public flip::DataModel <Model> {};
...code skipped...
void  run ()
{
   declare ();
   Document document (Model::use (), 123456789ULL, 'acme', 'gui ');
   Song & song = document.root <Song> ();                            (1)
   song.tempo = 120.0;                                               (2)
   document.commit ();                                               (3)
}
  1. This extract the unique Song, the root of the document.
  2. This sets the tempo of the song to 120 beats per minutes, now the document is marked as modified.
  3. This will apply the change to the document.

Commiting a document marks the boundary of the document change, which is computed into a transaction.

Working with Arrays

For this example, let's suppose that we want to insert a new track into the song. We suppose here that the document is already set up.

class Song : public flip::Object
{
public:
   static void    declare ();
   Track &        add_track ();
   flip::Float    tempo;
   flip::Array <Track>
                  tracks;
};
Track &  Song::add_track ()
{
   auto it = tracks.emplace (tracks.end ());    (1)
   return *it;                                  (2)
}
  1. Emplace a new track constructed in-place before the end () iterator. This is similar to push a new element at the back of the Array
  2. Dereference the returned iterator

Note: One should note that in the above example, the modification is not commited to the document.

Working with Collections

For this example, let's suppose that we want to insert a new clip into the track.

class Clip : public flip::Object
{
public:
   static void    declare ();
                  Clip () = default;
                  Clip (double position, double duration);
   flip::Float    position;
   flip::Float    duration;
};
class Track : public flip::Object
{
public:
   static void    declare ();
   Clip &         add_clip (double position, double duration);
   flip::Collection <Clip>
                  clips;
};
Clip &   Track::add_clip (double position, double duration)
{
   auto it = clips.emplace (clips.end (), position, duration);    (1)
   return *it;                                                    (2)
}
  1. Emplace a new clip constructed in-place with the second constructor of Clip
  2. Dereference the returned iterator

Note: One should note that in the above example, the modification is not commited to the document.

Working with Maps

For this example, let's suppose that we want to insert a new parameter into the track.

class Parameter : public flip::Object
{
public:
   static void    declare ();
                  Parameter () = default;
                  Parameter (double value);
   flip::Float    value;
};
class Track : public flip::Object
{
public:
   static void    declare ();
   Parameter &    add_parameter (std::string name, double value);
   flip::Map <Parameter>
                  parameters;
};
Parameter & Track::add_parameter (std::string name, double value)
{
   auto it = parameters.emplace (name, value);    (1)
   return *it;                                    (2)
}
  1. Emplace a new parameter constructed in-place with the second constructor of Parameter
  2. Dereference the returned iterator

Note: One should note that in the above example, the modification is not commited to the document.

Working with Variants

For this example, let's suppose that the clip has some content which can be either audio or midi content. Let also suppose that no content is not permitted.

To do that, you would typically use a Variant.

class Content : public flip::Object
{
public:
   static void    declare ();
};
class ContentAudio : public Content
{
public:
   static void    declare ();
   flip::String   url;
};
class ContentMidi : public Content
{
public:
   static void    declare ();
   flip::Blob     midi_events;
};
class Clip : public flip::Object
{
public:
   static void    declare ();
                  Clip () = default;
                  Clip (double position, double duration);
   flip::Float    position;
   flip::Float    duration;
   Variant <Content>
                  content;
};
class Track : public flip::Object
{
public:
   static void    declare ();
   Clip &         add_clip_audio (double position, double duration);
   flip::Collection <Clip>
                  clips;
};
Clip &   Track::add_clip_audio (double position, double duration, std::string url)
{
   auto it = clips.emplace (clips.end (), position, duration);
   auto & clip = *it;
   clip.content.reset <ContentAudio> ().url = url;    (1)
   return clip;
}
  1. Set the content of the clip to be an audio content with its url

Note: One should note that in the above example, failure to provide an initial value to the variant at commit time will trigger a flip validation failure.

Working with Optionals

For this example, let's suppose that the clip has some content which can be either audio or midi content. Let also suppose that no content is allowed this time.

To do that, you would typically use a Optional.

class Content : public flip::Object
{
public:
   static void    declare ();
};
class ContentAudio : public Content
{
public:
   static void    declare ();
   flip::String   url;
};
class ContentMidi : public Content
{
public:
   static void    declare ();
   flip::Blob     midi_events;
};
class Clip : public flip::Object
{
public:
   static void    declare ();
                  Clip () = default;
                  Clip (double position, double duration);
   flip::Float    position;
   flip::Float    duration;
   Optional <Content>
                  content;
};
class Track : public flip::Object
{
public:
   static void    declare ();
   Clip &         add_clip_audio (double position, double duration);
   flip::Collection <Clip>
                  clips;
};
Clip &   Track::add_clip_audio (double position, double duration, std::string url)
{
   auto it = clips.emplace (clips.end (), position, duration);
   auto & clip = *it;
   clip.content.reset <ContentAudio> ().url = url;                      (1)
   return clip;
}
Clip &   Track::add_clip_no_content (double position, double duration)  (2)
{
   auto it = clips.emplace (clips.end (), position, duration);
   return *it;
}
  1. Set the content of the clip to be an audio content with its url
  2. Set the content of the clip to none

Now that we know how to modify the model in different situations, the next part will be about observing those changes with an observer, and is covered in the next chapter Observing the Model.