TargetRegistry.h

// TargetRegistry.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2007

@import "Object.h"
#include "Ceda/cxUtils/xvector.h"

/*
Targets
-------

Definition : Each DLL or EXE is a /target/.  A target is uniquely identified by its /targetName/.

E.g. "Ceda/cxObject" is a target name.  Note that target names make use of namespaces.  A target name
shouldn't include version numbers.

Definition : C is a /sub-target/ of P (written C < P) if P statically links directly or indirectly 
against C.

Note that the sub-target relation is irreflexive, anti-symmetric and transitive.

E.g. Ceda/cxObject is a sub-target of Ceda/cxPersistStore.

Note that we can distinguish between direct and indirect sub-targets.

Target Index Numbers
--------------------

cxObject contains a threadsafe singleton registry called the /TargetRegistry/ that can be used by 
a target within a running process to obtain its unique /Target Index Number/ or (TIN), an integer of
type ssize_t.  

The TargetRegistry records the 1-1 mapping between targetName and TIN.  A target will register 
itself, obtaining its TIN when it is first loaded into the process.  A target records its TIN in a 
static global variable named s_localTin so it is local to the target.

Definition : Let tin(T) be the TIN assigned to target T (within a given running process).

Release sequence numbers
------------------------

Each target has its own /Release Sequence Number/ (RSN) to uniquely identify each formal release of
the target. RSNs start at 0.

Consider target T4 with sub-targets T1,T2,T3.   Then we can build a table for the releases of T4 
showing the corresponding RSNs of T1,T2,T3.  For example


    T4        Date         T1     T2     T3
    RSN                    RSN    RSN    RSN
    ------------------------------------------
     0    23 Feb 2008      14      3      0
     1    18 Jan 2009      16      3      0
     2    09 Mar 2009      17      3      4
     3    14 Aug 2009      20      3      6
     
Definition:  Given target T,  CurrentRSN(T) is the last RSN in the table for T.

For example, CurrentRSN(T4) = 3

Definition:  Given target T,  Rsns(T) = { r | 0 <= r <= CurrentRSN(T) } is the set of valid RSNs 
of T

Definition:  Let S be a sub-target of T. Let r in Rsns(T).  Then SubTargetRsn(T,r,S) gives the 
RSN of S corresponding to release r of T.

Eg  SubTargetRsn(T4,2,T3) = 4

T1,T2,T3 may in turn have sub-targets so they will have similar tables.  Note for example 
that if T1 is a sub-target of T3 then the above column of T1 RSNs would be redundant and would not be 
directly recorded by the application programmer.

Mapping RSNs
------------

When a target is first loaded into a process, it allocates and populates a multidimensional array 
used to map its RSNs to all direct or indirect sub-target RSNs.

Definition:
    Given target T,  let SubTargetRsnMap(T) be a pointer to an array of pointers to arrays of 
    integers (of type ssize_t), such that 
    
    SubTargetRsnMap(T)[r][s] = SubTargetRsn(T,r,S)
    
    where S is a sub-target of T having s = tin(S), and r in Rsns(T) 

Note that SubTargetRsnMap(T)[r] is the address of an array of integers
*/

namespace ceda
{

///////////////////////////////////////////////////////////////////////////////////////////////////
// Date

$struct+ Date
{
    int16 year;
    int8 month;    // 1-12
    int8 day;      // 1-31
};

@api void Serialise(Archive& ar, const Date& x);
@api void Deserialise(InputArchive& ar, Date& x);
@api xostream& operator<<(xostream& os, const Date& x);

///////////////////////////////////////////////////////////////////////////////////////////////////
// dsTargetRelease

struct dsTargetRelease0
{
    ConstStringZ m_name;              // Name of the release
    Date m_date;                      // Date of the release
};

template <ssize_t n>
struct dsTargetRelease
{
    ConstStringZ m_name;              // Name of the release
    Date m_date;                      // Date of the release
    RSN m_directSubTgtRsns[n];
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// dsTarget

$struct+ dsTarget
{
    ConstStringZ m_targetName;         // Fully qualified name of the target
    ssize_t m_numDirectSubTargets;
    ConstStringZ const* m_directSubTargetNames;
    ssize_t m_numReleases;
    const dsTargetRelease<1>* m_releases;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// XTarget

$adt+ XTarget
{
    void Unregister();
    const dsTarget& GetdsTarget();
    TIN GetTin();
    @for (rType in mRegistryTypes)
    {
        @def R = Reflected@@rType
        void RegisterReflected@@rType(const R* e);
        const xvector<const R*>& Get@@rType@@Registry() const;
        
    }
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// Target registration

@api XTarget* RegisterTarget(const dsTarget& dst);

// Find the target with the given name.  Returns nullptr if not found
@api XTarget* FindTarget(ConstStringZ targetName);

// Find the target with the given TIN.  
@api XTarget* FindTarget(TIN tin);

@api void GetTargets(xvector<XTarget*>& targets);

@api void WriteDataSourceRsn(Archive& ar, RSN rsn);

// Called at the beginning of the deserialisation of a datasource, in order to read the RSN from
// the archive a set thread local storage as required so that subsequent calls to GetModelRsn()
// work as expected.
@api void ReadDataSourceRsn(InputArchive& ar, XTarget* tgt);

// Called when deserialising a model embedded under a datasource, perhaps in a different DLL to
// where the datasource is defined.
@api RSN GetModelRsn(TIN localTin);

@runpython
{
    # convert the given targetName into a valid identifier by replacing forward slash characters 
    # with underscores
    def targetNameAsIdentifier(s):
        i = 0
        r = ''
        while i < len(s):
            c = s[i]
            if c == '/':
                r = r + '_'
            else:
                r = r + c
            i = i+1
        return r
}

@defpython CEDA_TARGET_NAME_TO_IDENTIFIER(s) =
{
    targetNameAsIdentifier(@str(s))
}

/*
Typically used for targets which don't define $models, and therefore have no need for declaring 
subtargets and releases 
*/
@def mRegisterTargetNotSupportingModels =
{
    @def tgtNameId = CEDA_TARGET_NAME_TO_IDENTIFIER(@root_to_projdir_path)
    namespace ceda
    {
    static dsTarget s_dsTarget =
        {
            @str(@root_to_projdir_path),
            0,
            nullptr,
            0,
            nullptr
        };
    static XTarget* s_xTarget = nullptr;
    XTarget* tgtNameId@@_GetXTarget()
    {
        if (s_xTarget == nullptr)
        {
            s_xTarget = RegisterTarget(s_dsTarget);
        }
        return s_xTarget;
    }
    $init
    {
        tgtNameId@@_GetXTarget();
    }
    } // ceda
}

// Declare in a cpp file *outside a namespace* within a target in order to declare its s_localTin
// and have it registered when the DLL is loaded.
@def mRegisterTargetHelper(subTgtNames,tgtReleases, bool leaf) =
{
    @def tgtNameId = CEDA_TARGET_NAME_TO_IDENTIFIER(@root_to_projdir_path)
    namespace ceda
    {
    @if (leaf)
    {
        static dsTargetRelease0 s_targetReleases[] = tgtReleases;
        static dsTarget s_dsTarget =
            {
                @str(@root_to_projdir_path),
                0,
                nullptr,
                cxArraySize(s_targetReleases),
                (dsTargetRelease<1>*) s_targetReleases
            };
    }
    @else
    {
        static ConstStringZ s_subTargetNames[] = subTgtNames;
        static dsTargetRelease<cxArraySize(s_subTargetNames)> s_targetReleases[] = tgtReleases;
        static dsTarget s_dsTarget =
            {
                @str(@root_to_projdir_path),
                cxArraySize(s_subTargetNames),
                s_subTargetNames,
                cxArraySize(s_targetReleases),
                (dsTargetRelease<1>*) s_targetReleases
            };
    }
    static TIN s_localTin = -1;
    static RSN s_localRsn = cxArraySize(s_targetReleases) - 1;
    static XTarget* s_xTarget = nullptr;
    RSN tgtNameId@@_GetModelRsn()
    {
        return GetModelRsn(s_localTin);        
    }
    void tgtNameId@@_WriteDataSourceRsn(Archive& ar)
    {
        WriteDataSourceRsn(ar,s_localRsn);    
    }
    void tgtNameId@@_ReadDataSourceRsn(InputArchive& ar)
    {
        ReadDataSourceRsn(ar,s_xTarget);
    }
    XTarget* tgtNameId@@_GetXTarget()
    {
        if (s_xTarget == nullptr)
        {
            s_xTarget = RegisterTarget(s_dsTarget);
            s_localTin = GetTin(s_xTarget);
        }
        return s_xTarget;
    }
    $init
    {
        tgtNameId@@_GetXTarget();
    }
    } // ceda
}

/*
Used for a target defining $models for which there are no subtargets defining $models

A new release must be declared when any $models within the target undergo schema evolution.

These releases may correspond to a proper subset of the formal releases of the target
because changes to functionality without involving schema evolution of a $model do not require
a release to be declared in mRegisterLeafTarget.

Example:

    mRegisterLeafTarget(
        {
            // version   year  mon  day
            {   "0.1",   {2015, 01, 01}  },      // Release #0
            {   "0.5",   {2017, 03, 23}  },      // Release #1
            {   "1.0",   {2018, 09, 06}  },      // Release #2
        })
*/
@def mRegisterLeafTarget(tgtReleases) = mRegisterTargetHelper(,tgtReleases,true)

/*
Used for a target defining $models for which there are subtargets defining $models

The declared subtargets are only the /direct subtargets/.  The framework calculates the 
transitive closure to give both the direct and indirect subtargets.

It is only necessary to define direct subtargets for libraries which define models which are 
directly /embedded/ within models of this target.  In theory one could link against a library 
but not be embedding any of its models; there is no need to declare a dependency in such cases.

A new release must be declared when any models within the target or models embedded within the
target undergo schema evolution.  That means that typically when a subtarget has a new release, it is 
necessary to also add a release to this target (so there's kind of a domino effect)

Example:

    mRegisterTarget(
        // Names of the sub-targets
        {  
            "Ceda/cxModel"
        },

        // Releases
        {
            // version   year mon day    cxModel
            { "0.1",    {2015, 01, 01},     0   },     // Release #0
            { "0.7",    {2015, 02, 24},     4   },     // Release #1
            { "0.23",   {2015, 03, 12},     5   },     // Release #2
            { "1.08",   {2015, 07, 15},     5   },     // Release #3
            { "2.04",   {2015, 11, 31},     6   },     // Release #4
        })
*/
@def mRegisterTarget(subTgtNames,tgtReleases) = mRegisterTargetHelper(subTgtNames,tgtReleases,false)

} // namespace ceda