Modern programs should use TStream class and its many descendants to do input / output. It has many useful descendants, like TFileStream, TMemoryStream, TStringStream.
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses
SysUtils, Classes;
var
S: TStream;
InputInt, OutputInt: Integer;
begin
InputInt := 666;
S := TFileStream.Create('my_binary_file.data', fmCreate);
try
S.WriteBuffer(InputInt, SizeOf(InputInt));
finally
FreeAndNil(S);
end;
S := TFileStream.Create('my_binary_file.data', fmOpenRead);
try
S.ReadBuffer(OutputInt, SizeOf(OutputInt));
finally
FreeAndNil(S);
end;
WriteLn('Read from file got integer: ', OutputInt);
end.
In the Castle Game Engine: You should use the Download function to create a stream that obtains data from any URL. Regular files, HTTP and HTTPS resources, Android assets and more are supported this way. Moreover, to open the resource inside your game data (in the data subdirectory) use the special castle-data:/xxx URL. Examples:
EnableNetwork := true;
S := Download('https://castle-engine.io/latest.zip');
S := Download('file:///home/michalis/my_binary_file.data');
S := Download('castle-data:/gui/my_image.png');
To read text files, we advise using the TCastleTextReader class. It provides a line-oriented API, and wraps a TStream inside. The TCastleTextReader constructor can take a ready URL, or you can pass there your custom TStream source.
Text := TCastleTextReader.Create('castle-data:/my_data.txt');
try
while not Text.Eof do
WriteLnLog('NextLine', Text.ReadLn);
finally
FreeAndNil(Text);
end;
Documentation of all the Castle Game Engine features to load and save streams, including the Download function and the TCastleTextReader class, is on https://castle-engine.io/url .
7.2. Containers (lists, dictionaries) using generics
The language and run-time library offer various flexible containers. There are a number of non-generic classes (like TList and TObjectList from the Contnrs unit), there are also dynamic arrays (array of TMyType). But to get the most flexibility and type-safety, I advise using generic containers for most of your needs.
The generic containers give you a lot of helpful methods to add, remove, iterate, search, sort… The compiler also knows (and checks) that the container holds only items of the appropriate type.
There are three libraries providing generics containers in FPC now:
We advise using the Generics.Collections unit. The generic containers it implements are
-
packed with useful features,
-
very efficient (in particular important for accessing dictionaries by keys),
-
compatible between FPC and Delphi,
-
the naming is consistent with other parts of the standard library (like the non-generic containers from the Contnrs unit).
In the Castle Game Engine: We use the Generics.Collections intensively throughout the engine, and advise you to use Generics.Collections in your applications too!
Most important classes from the Generics.Collections unit are:
- TList
-
A generic list of types.
- TObjectList
-
A generic list of object instances. It can "own" children, which means that it will free them automatically.
- TDictionary
-
A generic dictionary.
- TObjectDictionary
-
A generic dictionary, that can "own" the keys and/or values.
Here’s how to use a simple generic TObjectList:
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = {$ifdef FPC}specialize{$endif} TObjectList<TApple>;
var
A: TApple;
Apples: TAppleList;
begin
Apples := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.Add(A);
A := TApple.Create;
A.Name := 'another apple';
Apples.Add(A);
Writeln('Count: ', Apples.Count);
Writeln(Apples[0].Name);
Writeln(Apples[1].Name);
finally FreeAndNil(Apples) end;
end.
Note that some operations require comparing two items, like sorting and searching (e.g. by Sort and IndexOf methods). The Generics.Collections containers use a comparer for this. The default comparer is reasonable for all types, even for records (in which case it compares memory contents, which is a reasonable default at least for searching using IndexOf).
When sorting the list you can provide a custom comparer as a parameter. The comparer is a class implementing the IComparer interface. In practice, you usually define the appropriate callback, and use TComparer<T>.Construct method to wrap this callback into an IComparer instance. An example of doing this is below:
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
{$ifdef VER3_0} {$define GENERICS_CONSTREF} {$endif}
{$ifdef VER3_2_0} {$define GENERICS_CONSTREF} {$endif}
{$ifdef VER3_2_2} {$define GENERICS_CONSTREF} {$endif}
uses SysUtils, Generics.Defaults, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = {$ifdef FPC}specialize{$endif} TObjectList<TApple>;
function CompareApples(
{$ifdef GENERICS_CONSTREF}constref{$else}const{$endif}
Left, Right: TApple): Integer;
begin
Result := AnsiCompareStr(Left.Name, Right.Name);
end;
type
TAppleComparer = {$ifdef FPC}specialize{$endif} TComparer<TApple>;
var
A: TApple;
L: TAppleList;
begin
L := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := '11';
L.Add(A);
A := TApple.Create;
A.Name := '33';
L.Add(A);
A := TApple.Create;
A.Name := '22';
L.Add(A);
L.Sort(TAppleComparer.Construct({$ifdef FPC}@{$endif} CompareApples));
Writeln('Count: ', L.Count);
Writeln(L[0].Name);
Writeln(L[1].Name);
Writeln(L[2].Name);
finally FreeAndNil(L) end;
end.
The TDictionary class implements a dictionary, also known as a map (key → value), also known as an associative array. Its API is a bit similar to the C# TDictionary class. It has useful iterators for keys, values, and pairs of key→value.
An example using a dictionary:
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = {$ifdef FPC}specialize{$endif} TDictionary<String, TApple>;
var
Apples: TAppleDictionary;
A, FoundA: TApple;
ApplePair: {$ifdef FPC} TAppleDictionary.TDictionaryPair {$else} TPair<String, TApple> {$endif};
AppleKey: string;
begin
Apples := TAppleDictionary.Create;
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
if Apples.TryGetValue('apple key 1', FoundA) then
Writeln('Found apple under key "apple key 1" with name: ' +
FoundA.Name);
for AppleKey in Apples.Keys do
Writeln('Found apple key: ' + AppleKey);
for A in Apples.Values do
Writeln('Found apple value: ' + A.Name);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
A.Free;
finally FreeAndNil(Apples) end;
end.
The TObjectDictionary can additionally own the dictionary keys and/or values, which means that they will be automatically freed. Be careful to only own keys and/or values if they are object instances. If you set to "owned" some other type, like an Integer (for example, if your keys are Integer, and you include doOwnsKeys), you will get a nasty crash when the code executes.
An example code using the TObjectDictionary is below. Compile this example with memory leak detection, like fpc -gl -gh generics_object_dictionary.dpr, to see that everything is freed when program exits.
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = {$ifdef FPC}specialize{$endif} TObjectDictionary<String, TApple>;
var
Apples: TAppleDictionary;
A: TApple;
ApplePair: {$ifdef FPC} TAppleDictionary.TDictionaryPair {$else} TPair<String, TApple> {$endif};
begin
Apples := TAppleDictionary.Create([doOwnsValues]);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
finally FreeAndNil(Apples) end;
end.
If you prefer using the FGL unit instead of Generics.Collections, the most important classes from the FGL unit are:
- TFPGList
-
A generic list of types.
- TFPGObjectList
-
A generic list of object instances. It can "own" children.
- TFPGMap
-
A generic dictionary.
In FGL unit, the TFPGList can be only used for types for which the equality operator (=) is defined. For TFPGMap the "greater than" (>) and "less than" (<) operators must be defined for the key type. If you want to use these lists with types that don’t have built-in comparison operators (e.g. with records), you have to overload their operators as shown in the Operator overloading.
In the Castle Game Engine we include a unit CastleGenericLists that adds TGenericStructList and TGenericStructMap classes. They are similar to TFPGList and TFPGMap, but they do not require a definition of the comparison operators for the appropriate type (instead, they compare memory contents, which is often appropriate for records or method pointers). But the CastleGenericLists unit is deprecated since the engine version 6.3, as we advise using Generics.Collections instead.
If you want to know more about the generics, see Generics.
7.3. Cloning: TPersistent.Assign
Copying the class instances by a simple assignment operator copies the reference.
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := X;
Y.MyField := 123;
FreeAndNil(X);
end;
To copy the class instance contents, the standard approach is to derive your class from TPersistent, and override its Assign method. Once it’s implemented properly in TMyObject, you use it like this:
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := TMyObject.Create;
Y.Assign(X);
Y.MyField := 123;
FreeAndNil(X);
FreeAndNil(Y);
end;
To make it work, you need to implement the Assign method to actually copy the fields you want. You should carefully implement the Assign method, to copy from a class that may be a descendant of the current class.
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses
SysUtils, Classes;
type
TMyClass = class(TPersistent)
public
MyInt: Integer;
procedure Assign(Source: TPersistent); override;
end;
TMyClassDescendant = class(TMyClass)
public
MyString: string;
procedure Assign(Source: TPersistent); override;
end;
procedure TMyClass.Assign(Source: TPersistent);
var
SourceMyClass: TMyClass;
begin
if Source is TMyClass then
begin
SourceMyClass := TMyClass(Source);
MyInt := SourceMyClass.MyInt;
end else
inherited Assign(Source);
end;
procedure TMyClassDescendant.Assign(Source: TPersistent);
var
SourceMyClassDescendant: TMyClassDescendant;
begin
if Source is TMyClassDescendant then
begin
SourceMyClassDescendant := TMyClassDescendant(Source);
MyString := SourceMyClassDescendant.MyString;
end;
inherited Assign(Source);
end;
var
C1, C2: TMyClass;
CD1, CD2: TMyClassDescendant;
begin
C1 := TMyClass.Create;
C2 := TMyClass.Create;
try
C1.MyInt := 666;
C2.Assign(C1);
WriteLn('C2 state: ', C2.MyInt);
finally
FreeAndNil(C1);
FreeAndNil(C2);
end;
CD1 := TMyClassDescendant.Create;
CD2 := TMyClassDescendant.Create;
try
CD1.MyInt := 44;
CD1.MyString := 'blah';
CD2.Assign(CD1);
WriteLn('CD2 state: ', CD2.MyInt, ' ', CD2.MyString);
finally
FreeAndNil(CD1);
FreeAndNil(CD2);
end;
end.
Sometimes it’s more comfortable to alternatively override the AssignTo method in the source class, instead of overriding the Assign method in the destination class.
Be careful when you call inherited in the overridden Assign implementation. There are two situations:
- Your class is a direct descendant of the
TPersistent class. (Or, it’s not a direct descendant of TPersistent, but no ancestor has overridden the Assign method.)
-
In this case, your class should use the inherited keyword (to call the TPersistent.Assign) only if you cannot handle the assignment in your code.
- Your class descends from some class that has already overridden the
Assign method.
-
In this case, your class should always use the inherited keyword (to call the ancestor Assign). In general, calling inherited in overridden methods is usually a good idea.
To understand the reason behind the above rule (when you should call, and when you should not call inherited from the Assign implementation), and how it relates to the AssignTo method, let’s look at the TPersistent.Assign and TPersistent.AssignTo implementations:
procedure TPersistent.Assign(Source: TPersistent);
begin
if Source <> nil then
Source.AssignTo(Self)
else
raise EConvertError...
end;
procedure TPersistent.AssignTo(Destination: TPersistent);
begin
raise EConvertError...
end;
|
Note
|
This is not the exact implementation of TPersistent. I copied the FPC standard library code, but then I simplified it to hide unimportant details about the exception message.
|
The conclusions you can get from the above are:
-
If neither Assign nor AssignTo are overridden, then calling them will result in an exception.
-
Also, note that there is no code in TPersistent implementation that automatically copies all the fields (or all the published fields) of the classes. That’s why you need to do that yourself, by overriding Assign in all the classes. You can use RTTI (runtime type information) for that, but for simple cases you will probably just list the fields to be copied manually.
When you have a class like TApple, your TApple.Assign implementation usually deals with copying fields that are specific to the TApple class (not to the TApple ancestor, like TFruit). So, the TApple.Assign implementation usually checks whether Source is TApple at the beginning, before copying apple-related fields. Then, it calls inherited to allow TFruit to handle the rest of the fields.
Assuming that you implemented TFruit.Assign and TApple.Assign following the standard pattern (as shown in the example above), the effect is like this:
-
If you pass TApple instance to TApple.Assign, it will work and copy all the fields.
-
If you pass TOrange instance to TApple.Assign, it will work and only copy the common fields shared by both TOrange and TApple. In other words, the fields defined at TFruit.
-
If you pass TWerewolf instance to TApple.Assign, it will raise an exception (because TApple.Assign will call TFruit.Assign which will call TPersistent.Assign which raises an exception).
|
Note
|
Remember that when descending from TPersistent, the default visibility specifier is published, to allow streaming of TPersistent descendants. Not all field and property types are allowed in the published section. If you get errors related to it, and you don’t care about streaming, just change the visibility to public. See the Visibility specifiers.
|