-
-
Notifications
You must be signed in to change notification settings - Fork 41
Changing properties that are records (like vectors in Castle Game Engine)
Various classes in Castle Game Engine have properties of record types. For example TVector3
(and other TVectorXxx
), TMatrixXxx
, TCastleColor
and TCastleColorRGB
(colors are actually just aliases to TVector3
or TVector4
).
We like record types for some reasons:
- assignment of records copies the value (not a reference/pointer to it),
- records have a clear layout in memory (e.g. you can pass
TVector3
directly and reliably to an external library like OpenGL), - an array of records is a one tight block in memory (which is good for cache, in case you e.g. want to sum all the vectors).
Do not take this as an advice to use records for everything. In fact, in most cases, designing your API using classes (not records) should be your first choice. Classes have a clearly defined constructors, destructors, virtual methods..., and these features are really helpful to properly manage the memory of complicated interconnected structures. But there are valid reasons for using records for some stuff -- like vectors in a game engine.
Remember to always set these records as a whole, not just modify their components. For example this is correct:
MyScene.Translation := MyScene.Translation + Vector3(10, 0, 0);
This is correct also:
var
V: TVector3;
begin
V := MyScene.Translation;
V.X := V.X + 10;
MyScene.Translation := V;
end;
Never modify the record properties like this:
// THIS IS INCORRECT EXAMPLE
MyScene.Translation.X := MyScene.Translation.X + 10;
Depending on the definition of TCastleScene.Translation
, this could lead to various behaviors:
-
If
Translation
is a property with a getter method (property Translation read GetTranslation write SetTranslation
), it does nothing.That's because it's equivalent to
MyScene.GetTranslation.X := MyScene.GetTranslation.X + 10;
, i.e. you just modify the value of a temporary record returned by a method.As a solace, note that C# has the same trap.
-
If
Translation
is a property with a direct field to read (property Translation read FTranslation write SetTranslation
), it modifies the underlying field but without calling the SetTranslation method.It's equivalent to
MyScene.FTranslation.X := MyScene.FTranslation.X + 10;
, i.e. you just modify the field, bypassing the setter. This can lead to various surprising consequences. E.g. in case ofTCastleScene.Translation
, it means that physics engine will not be notified about the change, andTCastleScene.WorldTransform
will albo not be updated. -
Only when
Translation
is a simple field (Translation: TVector3
), it will work as expected. There is no getter or setter method in this case, of course.
In each CGE release, the definition of TCastleScene.Translation
may change. In fact it already happened in this example (in CGE <= 6.4, we had case 1 (read GetTranslation
); in CGE >= 6.5, we have case 2 (read FTranslation
)). These changes are sometimes necessary (sometimes you introduce a "getter" method when the underlying implementation changes; sometimes you remove a "getter" method when the underlying implementation no longer needs it, and you want to optimize reading it).
So, you are only safe if you always set the translation like MyScene.Translation := ...
.