### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: Save record of array of record  (Read 1921 times)

#### valter.home

• Jr. Member
• Posts: 75
##### Save record of array of record
« on: April 30, 2021, 05:08:46 pm »
I would need to save a structure like the one below to disk.
Basically I draw several shapes on a background image and I should be able to save the work so I can reload it later.

Code: Pascal  [Select][+][-]
2.
3. Type
4.   TVertices = array of TPoint;
5.
6.   TFigure1 = record
7.     Shape: TVertices;
8.     Name: String;
9.     Color: TColor;
10.   end;
11.
12.   TFigure2 = record
13.     Shape: TVertices;
14.     Name: String;
15.     Color: TColor;
16.   end;
17.
18.   TFigure3 = record
19.     Shape: TVertices;
20.     Name: String;
21.     Color: TColor;
22.   end;
23.
24.   TContainer = record
25.     Figure1: array of TFigure1;
26.     Figure2: array of TFigure2;
27.     Figure3: array of TFigure3;
28.   end;

I found some examples but for a single records.
I think the correct way is to use a stream, could someone give me some information about it?

#### Bart

• Hero Member
• Posts: 4282
##### Re: Save record of array of record
« Reply #1 on: April 30, 2021, 06:01:50 pm »
Basically for managed types you first write the length of the data, then the data itself.
So for TVertices you first write the length of the TVertices instance, then for each TPoint you write X and Y.

Bart

#### lucamar

• Hero Member
• Posts: 4009
##### Re: Save record of array of record
« Reply #2 on: April 30, 2021, 06:22:15 pm »
Something like this (untested!):

Code: Pascal  [Select][+][-]
2.
3. Type
4.   TVertices = array of TPoint;
5.
6.   { All TFiguresX were the same, so it suffices with just one type }
7.
8.   { TFigure }
9.
10.   TFigure = record
11.     Shape: TVertices;
12.     Name: String;
13.     Color: TColor;
14.     procedure SaveToStream(AStream: TStream);
16.   end;
17.
18.   TContainer = record
19.     Figures1: array of TFigure;
20.     Figures2: array of TFigure;
21.     Figures3: array of TFigure;
22.   end;
23.
24.   {... etc ...}
25.
26. procedure TFigure.SaveToStream(AStream: TStream);
27. var
28.   APoint: TPoint;
29. begin
30.   AStream.WriteDWord(Length(Shape));
31.   for APoint in Shape do
32.     AStream.Write(APoint, SizeOf(TPoint));
33.   AStream.WriteAnsiString(Name);
34.   AStream.Write(Color, SizeOf(Color));
35. end;
36.
38. begin
40.   for i := 0 to Length(Shape)-1 do
44. end;
45.
46. {To save a "container" ...}
47. procedure SaveContainer(AContainer: TContainer; const AFileName: String);
48. var
49.   AStream: TFileStream;
50.   AFigure: TFigure;
51. begin
52.   AStream := TFileStream.Create(AFileName, fmOpenWrite or fmCreate);
53.
54.   AStream.WriteDWord(Length(AContainer.Figures1));
55.   for AFigure in AContainer.Figures1 do
56.     AFigure.SaveToStream(AStream);
57.
58.   AStream.WriteDWord(Length(AContainer.Figures2));
59.   for AFigure in AContainer.Figures2 do
60.     AFigure.SaveToStream(AStream);
61.
62.   AStream.WriteDWord(Length(AContainer.Figures3));
63.   for AFigure in AContainer.Figures3 do
64.     AFigure.SaveToStream(AStream);
65. end;
66.
67. { and conversely to load it back }

Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### Bart

• Hero Member
• Posts: 4282
##### Re: Save record of array of record
« Reply #3 on: April 30, 2021, 11:55:23 pm »
Alternatively use a class helper for TStream:
(Untested code)
Code: Pascal  [Select][+][-]
1. {\$mode objfpc}
2. {\$h+}
3.
4. uses
5.   SysUtils, Classes;
6.
7. type
8.   TColor = -\$7FFFFFFF-1..\$7FFFFFFF;
9.
10. type
11.   TVertices = array of TPoint;
12.
13.   TFigure = record
14.     Shape: TVertices;
15.     Name: String;
16.     Color: TColor;
17.   end;
18.
19.   TFigureArray = array of TFigure;
20.
21.   TContainer = record
22.     Figure1: TFigureArray;
23.     Figure2: TFigureArray;
24.     Figure3: TFigureArray;
25.   end;
26.
27.   { TStreamHelper }
28.
29.   TStreamHelper = class helper for TStream
30.     procedure WritePoint(P: TPoint);
31.     procedure WriteVertices(V: TVertices);
32.     procedure WriteFigure(F: TFigure);
33.     procedure WriteFigureArray(F: TFigureArray);
34.     procedure WriteContainer(C: TContainer);
35.   end;
36.
37. { THelper }
38.
39. procedure TStreamHelper.WritePoint(P: TPoint);
40. begin
41.   Self.WriteDWord(P.X);
42.   Self.WriteDWord(P.Y);
43. end;
44.
45. procedure TStreamHelper.WriteVertices(V: TVertices);
46. var
47.   DataSize: Int64;
48.   P: TPoint;
49. begin
50.   DataSize := Length(V);
51.   Self.WriteQWord(DataSize);
52.   for P in V do Self.WritePoint(P);
53. end;
54.
55. procedure TStreamHelper.WriteFigure(F: TFigure);
56. begin
57.   Self.WriteVertices(F.Shape);
58.   Self.WriteAnsiString(F.Name);
59.   Self.Write(F.Color, SizeOf(TColor));
60. end;
61.
62. procedure TStreamHelper.WriteFigureArray(F: TFigureArray);
63. var
64.   DataSize: Int64;
65.   Fig: TFigure;
66. begin
67.   DataSize := Length(C);
68.   Self.WriteQWord(DataSize);
69.   for Fig in F do Self.WriteFigure(Fig);
70. end;
71.
72. procedure TStreamHelper.WriteContainer(C: TContainer);
73. begin
74.   Self.WriteFigureArray(C.Figure1);
75.   Self.WriteFigureArray(C.Figure2);
76.   Self.WriteFigureArray(C.Figure3);
77. end;
78.
79. var
80.   FS: TFileStream;
81.   Container: TContainer;
82. begin
83.   //Create the container
84.   //...
85.   FS := TFileStream.Create('test.bin', fmCreate);
86.   FS.WriteContainer(Container);
87.   FS.Free;
88.   //Free the container
89. end.

Bart

#### valter.home

• Jr. Member
• Posts: 75
##### Re: Save record of array of record
« Reply #4 on: May 01, 2021, 01:04:06 pm »
I am currently working on lucamar's suggestion.

I wrote the procedure for reading the stream (for the moment only for one figure).

Code: Pascal  [Select][+][-]
1. procedure LoadContainer(AContainer: TContainer; const AFileName: String);
2. var
3.   AStream: TFileStream;
4.   AFigure: TFigure;
5.   i: integer;
6. begin
8.   //AStream.Seek(0,0);
10.   for AFigure in AContainer.Figure1 do
11.   begin
14.     for i := 0 to Length(AFigure.Shape)-1 do
15.       begin
17.         AFigure.Shape[i].Y.ToString);
18.       end;
19.   end;
20.   AStream.Free;
21. end;

It would seem to work but now I'm thinking about how to proceed. Do I have to iterate for each figure copying the data one by one or would there be a way to directly assign AContainer to TContainer with all its contents?
I have tried Move () with no success.
I haven't found enough information around.

#### lucamar

• Hero Member
• Posts: 4009
##### Re: Save record of array of record
« Reply #5 on: May 01, 2021, 01:51:34 pm »
You can't use Move() here, because there are managed types in your records (the strings) which are de facto pointers. The best, if not the only, solution then is to deal with each TFigure separately.

Note that your main bottleneck, in this case, is I/O and would be so even if you could find some "clever" solution. Unless you'll be dealing with transfers of thousands of TFigure from one container to another (in memory) a simple loop should be fast enough.

ETA: Unless that's not what you meant? Please, try to be as specific as you can to tell us what you want to achieve.

And please, please, please don't name variables like TContainer, leave that kind of name for types. Otherwise it's confusing because that's the "normal" convention:
- TSomething is the type, and
- ASomething, SomethingA, etc are variables of that type.
« Last Edit: May 01, 2021, 01:59:53 pm by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### valter.home

• Jr. Member
• Posts: 75
##### Re: Save record of array of record
« Reply #6 on: May 01, 2021, 03:22:47 pm »
Quote
Please, try to be as specific as you can to tell us what you want to achieve.

What I would like to achieve is this:
For example, I draw about ten polygons (at most they could be about fifty, no more), which can all have equal or different numbers of vertices, but also different properties. Name and color are present in all of them but some may have, for example, type of cursor when the mouse passes over it. So TFigure1, TFigure2 and TFigure3 are different records (sorry if I overlooked this in my post but I did it for simplicity).
Then I save the properties of the figures in a stream.
So I close the program, reopen it and I would like to load the saved work, that is to find all the figures in their original position with all their saved data.
With your suggestions I managed to save correctly in the stream and when I reload it the data are all present.
But at this point it's all in the stream and I should overwrite my TContainer with the contents of the stream.
For example, I draw 5 polygons, save them, delete them, draw others but I don't like them, I would like to load the ones I just saved.

Quote
Actually TContainer is not in fact a variable but it is the declared type. Then in the variables I have FContainer: TContainer;
Where in the code did you see this? So maybe I'm doing something wrong.

#### ASerge

• Hero Member
• Posts: 1796
##### Re: Save record of array of record
« Reply #7 on: May 01, 2021, 04:09:50 pm »
Code: Pascal  [Select][+][-]
1. procedure LoadContainer(AContainer: TContainer; const AFileName: String);
2. var
3.   AStream: TFileStream;
4.   AFigure: TFigure;
5.   i: integer;
6. begin
8.   //AStream.Seek(0,0);
10.   for AFigure in AContainer.Figure1 do
11.   begin
14.     for i := 0 to Length(AFigure.Shape)-1 do
15.       begin
17.         AFigure.Shape[i].Y.ToString);
18.       end;
19.   end;
20.   AStream.Free;
21. end;
Use "for..in" loop variable only as a read-only variable.
Because in this case, the compiler will copy the value from the array AContainer to the variable AFigure. Then, LoadFromStream will be called for this AFigure variable. However, the data in the AContainer array will not change.

In this case, the usual loop with the index is more appropriate.

#### jamie

• Hero Member
• Posts: 4565
##### Re: Save record of array of record
« Reply #8 on: May 01, 2021, 04:30:47 pm »
for controls that already exists, can't the existing TFiler, TReader, TWrite code be used here ?
The only true wisdom is knowing you know nothing

#### PascalDragon

• Hero Member
• Posts: 3056
• Compiler Developer
##### Re: Save record of array of record
« Reply #9 on: May 01, 2021, 04:42:44 pm »
The TFiler, TReader, TWriter can only be used together with TComponent and their descendants.

However a generic (de)serializer for records that only contain primitive types, other records and arrays (both static and dynamic) can be created by making use of the RTTI FPC generates (except for the field names however, so one would need to rely on the offset, so a binary representation would be possible, a JSON one less...).

#### lucamar

• Hero Member
• Posts: 4009
##### Re: Save record of array of record
« Reply #10 on: May 01, 2021, 05:31:49 pm »
I have found that, for records, using advanced records and adding LodFrom/SaveToStream (making use of TStream's specialized methods, if needed) is often the more convenient way to load/save them.

And since you can use any TStream descendant you can load/save records from almost anywhere, not just disk files, which is quite a bonus

Add to that SysUtils type helpers with their almost bewildering array of conversion methods and you can load/save in almost any (simple) format your heart desires.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.