Mike Torrettinni
Mike Torrettinni

Reputation: 1824

How to share records structure between projects, with slight variations?

I have 2 projects that work on same data, different processes. Main arrays are almost identical with slight variations, like:

// Project 1

TData1 = record
  A:string;
  B:integer;
  C:word;
  ...
end;

// Project 2

TData1 = record
  A:string;
  B:integer;
  C:word;
  ...
  XMLNode:TXMLNode; // Extra value needed in Project 2, not needed in Project 1
end;

I have numerous arrays that i want to share between projects. I would like to keep the same array structure so I can copy&paste any future changes that need to be implemented in both projects. Is there any way to keep records same with slight difference?

I was thinking of something like this:

    // in Project 1:

    TExtras = record
    end;

    // in Project 2:

    TExtras = record
      XMLNode:TXMLNode;
    end;

    // shared - in both projects

    TData1 = record
      A:string;
      B:integer;
      C:word;
      ...
      Extras:TExtras; // different extra fields based on project needs
    end;

And i can add additional fields into Extras in Project2 accessing fields with Data1.Extras.XMLNode. Not sure if this is future proof implementation.

The goal is to one day put all the shared structure into shared unit, one maintenance point and no more copy&paste. I need arrays to retain flexibility as simple arrays (array of TData1 or TArray<TData1>), so I can't go into complex implementation that will limit option to easily copy, sort, make distinct, manipulate data...

Is that correct approach? Any better ideas?


Edit:

Both projects work on same data, so they both read from the same 'source' files, but produce different end results. Right now I have lots of arrays and 99% of them are used for the same purpose in both projects, same functions. But now, when I work on one or the other project, adding new record fields, new functions that use new fields, and if I don't immediately synchronize the structure and new functions, it happens that in a few weeks I will need to to do the same in project 2 and I will create new fields with different names and different function names. So, when I finally copy some complex function between projects, I see they don't match only due to different naming.


Edit 2:

From all the comments and suggestions I have decided to go another route: to share common data structure and code in shared unit and create additional arrays with extra record fields in project 2. I would create these new arrays that link to main data arrays, to have:

// shared data 
TData1 = record
  A:string;
  B:integer;
  C:word;
  ...
end;

Data1:TArray<TData1>;

// additional in Project 2
TDataExtra = record
  DataIdx:integer;// link to TData1
  XMLNode:TXMLNode;
  ...
end; 

DataExtras:TArray<TDataExtra>;

to have simple access to XMLNode value for each Data1 record:

fGetXMLNode(i); // where i is index in Data1 array and function will return XMLNode

I believe with this I can keep shared units and add any extras to any array, with the minimal extra work, which is still lower cost than maintaining 2 data structure and code.

Would that be better solution?

Upvotes: 2

Views: 240

Answers (3)

Mike Torrettinni
Mike Torrettinni

Reputation: 1824

Easy solution is to keep the data structure and related functions shared between both projects and add additional arrays that contain additional data, specific to each project. It will bring slightly more work to use extra arrays, but will keep shared code really shared.

So, instead of new classes or IFDefs, as proposed in other anwers, a simple extra arrays that link to main data are best option for the problem:

// shared main data through both projects
TData1 = record
  A:string;
  B:integer;
  C:word;
  ...
end;

Data1:TArray<TData1>;

// additional in Project 2
TDataExtra = record
  DataIdx:integer;// link to TData1
  XMLNode:TXMLNode;
  ...
end; 

DataExtras:TArray<TDataExtra>;

With this solution, any additional arrays, like DataExtras or any other extra fields for other arrays, are easy to add, expand, without needing to change shared code. Shared code will be easy to maintain, as it only holds main data and nothing specific to only one project

Upvotes: 2

Arioch &#39;The
Arioch &#39;The

Reputation: 16065

Pascal does not have void/Unit datatype, still you can kind of simulate it. The compiler does not always feel nice about it, in some paths it just makes no assumption/check that in some extreme cases SizeOf(someType) might be zero.

Still, if you really need to squeeze every last byte in Project 2 while keeping sources shared with Project 1 there can be used this ugly hack:

File Project_1.inc

{$IFnDEF THIS_IS_PROJECT_1}
halt compilation! wrong setup!!!
{$EndIf}

{$IFDEF THIS_IS_PROJECT_2}
halt compilation! wrong setup!!!
{$EndIf}

type TDataPayload = TXMLNode;

File Project_2.inc

{$IFnDEF THIS_IS_PROJECT_2}
halt compilation! wrong setup!!!
{$EndIf}

{$IFDEF THIS_IS_PROJECT_1}
halt compilation! wrong setup!!!
{$EndIf}

type REmpty = packed record end;

type TDataPayload = REmpty;

// after compilation - call `assert(SizeOf(TDataPayload) = 0);`

File CommonDataType.pas

{$IfDef THIS_IS_PROJECT_1}
  {$INCLUDE Project_1.inc}
{$EndIf}

{$IfDef THIS_IS_PROJECT_2}
  {$INCLUDE Project_2.inc}
{$EndIf}

TData1 = 
{$IFDEF THIS_IS_PROJECT_2}
  packed
{$EndIf}
  record
    A:string;
    B:integer;
    C:word;
  ...
    Payload: TDataPayLoad;
  end;

That being said, your assessment that you can not waste a single byte via type TXMLNode=byte compatibility stub in the project 2 seems very dubious to me. Because:

  1. you use fast memory-padded record instead of slow bytes-squeezing packed record
  2. you use TXMLNode, even if in a different project - every TObject instance would occupy dozens of bytes anyway, pointers to RTTI, pointers to VMT, supporting frames in Heap Manager.... The very pointer to it in the record would additionally occupy 4 or 8 bytes. And you cannot afford just ONE extra byte instead of all that??? Come on!
  3. Your very A:string; spends 4 (or 8) bytes per the pointer inside every record and then 12 bytes more as StringRec header, and then unknown pre-allocated just-in-case buffer after the string content, and then two extra bytes for trailing #0, and then supporting data structures in the Heap Manager. Still just ONE extra byte of type TXMLNode=boolean stub is way too much???
  4. if your application performance/correctness is dependent upon just one extra byte per data frame, that also means that as the user would generate 5% more data than usual your application would break as well. Period. If to trust your assessment, then your application already hit the bar, already is past the algorithms/design limits. And should be remodeled to use memory-unbound algorithms. A simple example - instead of using in-memory Quicksort you could use on-disk Streaming Sort, it has the same worst-case complexity but does only need memory for 4 data frames.

Upvotes: 2

Ken Bourassa
Ken Bourassa

Reputation: 6502

It is going to be hard to say which solution would be best/better without the full context, but here's a few other methods that can achieve the same.

Conditional define

TData1 = record
  A:string;
  B:integer;
  C:word;
  [...]
{$IFDEF NEEDXMLNODEINTDATA1}
  XMLNode:TXMLNode; // different extra fields based on project needs
{$ENDIF}
end;

Use a new structure

Use a different structure in your 2nd application. I would expect this to be the right approach for most cases.

TData1Node = record
  Data1 : TData1;
  XMLNode : TXMLNode;
end;

Include XMLNode all the time

Since it's just a pointer, unless you have an absurd amount of records in your application, it would barely register on memory usage.

Upvotes: 2

Related Questions