Skip to content

Changing properties that are records (like vectors in Castle Game Engine)

Michalis Kamburelis edited this page Jan 3, 2019 · 10 revisions

Castle Game Engine defines various record types. For example TVector3 (and other TVectorXxx), TMatrixXxx, TCastleColor and TCastleColorRGB (colors are actually just aliases to TVector3 or TVector4).

Various classes have properties of record types. On this page we use, as an example, the TCastleTransform.Translation property, of type TVector3. This is a very often used property, it moves an object (3D or 2D, of TCastleTransform or TCastleScene class).

Why Using Records?

We like record types for some reasons:

  • simple assignment of records (A := B) copies the value (not a reference/pointer to it), and is fast,
  • 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 clearly defined constructors, destructors, virtual methods, and more, and these features are really helpful to properly manage the memory of complicated interconnected structures. The point is: there are valid reasons for using records for some stuff -- like vectors in a game engine.

How To Modify Record Properties

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;

How To NOT Modify Record Properties

Never modify the record properties like this:

// ALL THESE LINES ARE EQUIVALENT, AND INCORRECT! THEY DON'T DO WHAT YOU EXPECT.
MyScene.Translation.X := MyScene.Translation.X + 10;
MyScene.Translation[0] := MyScene.Translation[0] + 10;

// This line is (fortunately) a compiler error:
// MyScene.Translation.Data[0] := MyScene.Translation.Data[0] + 10; 

Depending on the definition of TCastleScene.Translation, this could lead to various behaviors:

  1. 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.

  2. 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 of TCastleScene.Translation, it means that physics engine will not be notified about the change, and TCastleScene.WorldTransform will also not be updated.

  3. Only when Translation is a simple field (Translation: TVector3, not a property), 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 CGE <= 6.4, we had 1st case, read GetTranslation; in CGE >= 6.5, we have 2nd case, read FTranslation). These changes are sometimes necessary (sometimes we introduce a "getter" method when the underlying implementation changes, and a getter is needed e.g. to get a value from internal instance of something, or to cache some calculations; sometimes you remove a "getter" method when the underlying implementation no longer needs it, and you want to optimize reading this property).

So, you are only safe if you always set the translation like MyScene.Translation := ....