How to work with Pointers rightly?

Good day!
I use many same objects in the game world. And, if location contain 100 trees, I don’t like to create 100 TTree objects, of course (for now I create it yet, and this work fast, but it seems not optimal, especially I want to make more objects and more locations).
Please, recommend me, how to optimize program better.
For now my algorithm is:

  1. Creating of the array with all objects what I need in a single copy. One tree, one cliff, one home etc.
  2. Making array with pointers in every game location. Position [i, j] of pointer in this array = position of TCastleImageTransform [i, j] in CastleScene.
  3. If some tree will take damage, program make new special tree with lower HP and reassign pointer to him (and in the general array will be two trees).

It is a good way, or i can make something better?

In This short example I really create only two Trees, as i understand? When program create array of Trees, memory for 10 TTree objects, or only for 10 pointers uses?
And when ^ pointers are needed (I use it now for little things, but usually only to make code more compact and readable) ? In what cases this things are necessary? They says, some programmers don’t like pointers.

program ExTree;
uses
  TreeUnit;
var
  Tree01, Tree02: TTree;
  Trees: array [0..9] of TTree;
  i: Integer;
begin
  Tree01 := TTree.Create;
  Tree02 := TTree.Create;
  for i := 0 to 9 do
      Trees[i] := Tree01;
  Trees[2] := Tree02;
  Tree02.Name := 'AnotherTree';
  for i := 0 to 9 do
      WriteLn(Trees[i].Name);
end.   
unit TreeUnit;
interface
uses
  Classes;
type
  TTree = class
  public
    Name: String;
    constructor Create;
  end;
implementation
constructor TTree.Create;
begin
  Name := 'Tree';
end;
end.

Thanks for attention to my question :upside_down_face:

Object/Class references are pointers. Trees contains 9 references to the same tree (01) and you overwrote [2] with Tree02, so one reference to that. But you only instantiated 2 trees. Though these are technically pointers you aren’t really using them like pointers (like walking to the next one or other pointer stuff). You don’t use ^ with objects/classes (anymore). It is implied. Assuming the trees are CGE objects, they are reference counted and will free themselves when count=0. But if they aren’t, then you will need to free them and be sure not to free Tree01 more than 1 time.

You can use pointers more deeply to speed up loops

type
PTree = ^TTree;
var TreePtr : PTree;
TreePtr := @Trees[0];
for i := 0 to 9 do
begin
WriteLn(TreePtr^.Name);
inc(TreePtr);
end;

This is an example of making an actual pointer to the first tree and then walking the items of the list with a pointer. It uses only addition and not multiplication as Tree[i] would do. So that is faster. i is not used inside of the loop, so that also makes it faster (at least for Delphi, not sure about Object Pascal).
If you then count the loop backward downto 0 that also makes it faster on Delphi (as long as i isn’t referenced in loop).

1 Like

Thanks!
In real code, for example, TTree include link to the sprite of tree in data folder. And, when I draw a scene, i do approximately this:

SceneImages: array of array of TCastleImageTransform;`
...
for i := 0 to SceneWidth do
  for j := 0 to SceneHeight do
   SceneImages[i][j].Url := Trees[i][j].ObjectImg;

If position are empty (they links to the object with zero name, but not ‘Tree’ or something else) SceneImages[i][j].Exists := False. For now i think how to do it all more compact and light…

I’m trying to think of the actual usecase. And I believe it’s not the bet approach. I mean, it can be done this way too, but this will render (SceneWidth+1)x(SceneHeight+1) independent images which in turn depending on how actually large this number is can be rather slow. First thing you are missing here is batching which can in the simplest scenario speed up drawing of your scene 100x and more.

Unfortunately I’m really afraid to teach you a wrong concept here, so most likely will ask @michalis to give a better explanation on how to solve this issue through TCastleImageTransform.

However, overall notes: if you have a lot of similar objects on the scene it’s much more effective to render them from a single texture (tileset), not from a set of different files. You can “prepare” such image beforehands through code if you want to change it depending on in-game situation or be more flexible with your tileset as it can change often during development. Then you can render elements of this tileset depending on the situation, which will be by far more efficient as in this situation you can render the whole map in a single draw call.

In my current game I am doing this through TDrawableImage which is not the recommended approach to do this kind of things. I think that TCastleImageTransform can hypothetically automatically offer you batching, however, I can’t say for sure. Eventually there is a way to optimize things even further by construction a custom TShape with all your images packed in, but this is far from trivial.

In your current approach (let’s imagine all trees are different and you can’t put them into a single texture for some reason) - using TCastleTransformReference would be a more effective way. It allows you to reference another transform and have “multiple copies” of it across the scene which I’m 95% sure will also do automatic batching.

This way your SceneImages will be array of array of TCastleTransformReference and Trees will be array of TCastleImageTransform which in turn aren’t added anywhere, like this:

var
  SceneImages: array of array of TCastleTransformReference;
  Trees: array of TCastleImageTransform;
...
for I := 0 to SceneWidth do
  for J := 0 to SceneWidth do
    SceneImages[I, J].Reference := Trees[TreeKindOnMap[I, J]];

One more tiny note, double arrays are not very efficient in whatever programming language. They seem convenient, but overall it’s almost as simple to work with a “linearized” array as with a 2D array:

for I := 0 to (SceneWidth + 1) * (SceneHeight + 1) - 1 do
  SceneImages[I].Reference := Trees[TreeKindOnMap[I]];
1 Like

This uses memory (a pointer’s worth) even where there are no trees. So if you have 3 trees you are still taking SceneWidth*SceneHeight*SizeOf(pointer) of memory. Instead of an array of every possible position you should just have an array of the trees that exist, sorted by their position.

Yes, but for now I have a little difficulties with connection between the image in the scene and game object. Every TCastleImageTransform have unique .Tag. And, when I click to image on scene, program use this .Tag to find game object.
Moreover, i need information about position in map of every game object, and this information is not in two trees objects in my example. For now this information - is position of Pointer in Pointer array. And, if Pointers array will be smaller then scene grid, how to say to the program where put the object image?
I think about making a little data type for contain Pointer + position in location. But if i will do it, I again use additional memory for this information.
What you think about this example? I think it will be more useful with real game objects with many properties…


program ExTree;

uses
  TreeUnit;
var
  Tree01, Tree02: TTree;
  Trees: array [0..9] of TTreeBox;
  i: Integer;
begin
  Tree01 := TTree.Create;
  Tree02 := TTree.Create;
  for i := 0 to 9 do
      Trees[i] := TTreeBox.Create(Tree01, (i+1)*2, (i+1)*3);
  Trees[2].Contents := Tree02;
  Tree02.Name := 'AnotherTree';
  for i := 0 to 9 do
      WriteLn(Trees[i].Contents.Name, ' - ',
              Trees[i].PosX, ' - ',
              Trees[i].PosY);
end.
unit TreeUnit;

interface

uses
  Classes;
type
  TTree = class
  public
    Name: String;
    constructor Create;
  end;

  TTreeBox = class
  public
    Contents: TTree;
    PosX, PosY: Integer;
    constructor Create(c: TTree; x, y: Integer);
  end;

implementation

constructor TTree.Create;
begin
  Name := 'Tree';
end;

constructor TTreeBox.Create(c: TTree; x, y: Integer);
begin
  Contents := c;
  PosX := x;
  PosY := y;
end;

end.

Tree - 2 - 3
Tree - 4 - 6
AnotherTree - 6 - 9
Tree - 8 - 12
Tree - 10 - 15
Tree - 12 - 18
Tree - 14 - 21
Tree - 16 - 24
Tree - 18 - 27
Tree - 20 - 30

Tile Set is single image, what slised by program to small textures? I must to draw big picture and use pixels positions to dig single textures for objects?

Thanks, try to change it… I need [i] and [j] numbers to calculate fixed positions of objects, but I will try to get j like this: j := i div SceneWidth.

Yes. The tileset looks like this: data/map/tileset.png · master · EugeneLoza / Foxy Misfortunes · GitLab - in this specific situation a 1024x1024 image, containing (in the simple case - equally sized) 128x128 items. In case of TDrawableImage you have Draw(ScreenRect, ImageRect) - where ScreenRect is TFloatRectangle which corresponds to rectangle on screen, and ImageRect - correspondingly to a rectangle on the image (x, y, width, height). In case of a TShape this would be used through UV unwrap (made in Blender or other software, or designed by code). There was a commit that allowed to do the same for TCastleImageControl (New property TRegion for TCastleImagePersistent allows selecting a cropped region from a UI image set by Freedomax · Pull Request #442 · castle-engine/castle-engine · GitHub) however, I’m not sure if it works for TCastleImageTransform.

I’d rather do it inverse. Keep I, J and then recalculate them into a linear I + Height*J - because the game logic happens in X-Y plane and hence converting those into “index” needs to be done only occasionally. Note, that 2D array simply does that for you. However, if you are using a linearized array you can do some optimizations and not always convert X-Y into index but work on index directly.

Sorry, I didn’t read that message carefully (at work now) but overall, if you need to add “additional information” to your objects it may be a good idea to make your own “derivative” of TCastleImageTransform. Tag is a good thing, but you can’t put everything into Tag - and using more eloquent metadata (potentially with special methods to handle object-related interactions) may be beneficial, e.g. like this:

type TMapElement = class(TCastleImageTransform)
public
  X, Y: Integer;
  Durability: Single;
  WasSeenByPlayer: Boolean;
  procedure HitByPlayer(const Damage: Single);
  function HarvestResources(const ToolQuality: Single): Single;
end;

You can also make this new TMapElement available in the Editor, see examples\advanced_editor\custom_component example on how to do that: The core of the whole thing is located in initialization section of gamecontrols.pas unit - roughly speaking it only needs to register a few serializable objects so that they can be manipulated. Also note editor_units="GameControls" line in CastleEngineManifest.xml. You can check documentation at Custom Components | Manual | Castle Game Engine for more details.

1 Like

Yes, I think I will do it for scene, thanks.
But is it good for “inner” game logic? Because, as I understand, TCastleImageTransform used for visualization and include many properties and methods, that doesn’t needs for persons and game world objects?
I use class TEssence = class(TObject) for game objects. And this class include address of image. Program convey this address to TCastleImageTransform.Url. In the same time, the click on TCastleImageTransform opens pointer in game location and get the Essence.
So, I have many transactions between scene and game logic? I did It for separating visualisation processes from objects of game world.

First of all: As with all optimizations, make sure you really need it :slight_smile:

The engine automatically uses a cache of textures. Even of you would just create 100 instances of TCastleImageTransform, completely independent, without any tricks we discuss in this thread, you just set the same URL – engine will actually load the texture only once (load from disk and load to GPU). As usual with optimizations, we sure that they are necessary – the engine does some things automatically :slight_smile:

Note that e.g. CGE examples/isometric_game/ just instantiates many TCastleImageTransform , that’s OK, not a problem (for memory or performance).

It also activates batching by MainViewport.DynamicBatching for fast drawing, and this is really good enough for a lot of cases.

Mostly yes, this is correct, and it is exactly an approach we describe on Writing code to modify scenes and transformations | Manual | Castle Game Engine – see “10. Multiple instances of the same scene”, and the example linked there – examples/viewport_and_scenes/cars_demo/, castle-engine/examples/viewport_and_scenes/cars_demo at master · castle-engine/castle-engine · GitHub .

With one difference: add a TCastleTransform to shift the position of each image instance. See the example linked above for details. So you can have 100 instances of (exactly) the TCastleTransform class, and in each of these 100 instances place a reference to the same thing (like the same TCastleImageTransform or TCastleScene reference).

The TCastleTransform that are parents should have different translations for it to make sense. You can also use them to record any other special properties of each instance, like a tag or anything else.

In other words:

  • yes, you can insert the same TCastleTransform (or any descendant of TCastleTransform, like TCastleScene or TCastleImageTransform) multiple times into the transformation hierarchy,

  • yes, it is a valid way to optimize memory usage.

  • For it to make sense, you probably want each of these instances to have a different translation. The way to achieve it is to wrap them in additional parents of TCastleTransform, that just translate them. The bare TCastleTransform class is cheap.

Engine will render the thing multiple times, yet the memory only contains a single instance. The downside of this optimization is that the thing you reference multiple times is naturally in the same “state” multiple times, e.g. if you play an animation on TCastleScene referenced multiple times – then the same animation is visible in all instances.

There are some alternatives, for completeness, like:

  • TCastleTransformReference, mentioned on Viewport with scenes, camera, navigation | Manual | Castle Game Engine . Underneath, it really achieves the same thing as above (allows to refer multiple times to one object), with similar drawbacks (it remains one object, so e.g. all instances show the same animation).

  • TCastleScene.Cache – but this doesn’t converse memory so efficiently. It has a common cache for “template” of the object". Maybe in the future (but not now) it will also allow to share non-dynamic nodes. So I don’t really advise this in your case.

As for “pointers” – well instance of a class is actually a pointer. I would I like pointers – and I would say that actually Pascal classes are somewhat a “comfortable way to implement many algorithms that (in older languages that didn’t have classes) required explicit pointer usage”. See Modern Object Pascal Introduction for Programmers | Castle Game Engine for more info, also on Pointers – Modern Object Pascal Introduction for Programmers | Castle Game Engine , " 8.8. Pointers".

Note that CGE doesn’t use reference counting for most classes, including components like TCastleTransform, TCastleImageTransform, TCastleScene. These components are freed following the standard TComponent rules – so you either free them manually, or you make sure that they have a proper “owner” that will free them. See Modern Object Pascal Introduction for Programmers | Castle Game Engine about freeing.

CGE uses reference counting only for X3D nodes (classes descending from TX3DNode, and placed in TCastleScene.Root). But not for everything :slight_smile:

Batching can indeed be used to speed-up the rendering. Like examples/isometric_game/ .

It does solve a different problem (batching speeds up rendering), and is independent from optimizing memory usage in this sense.

Indeed, drawing using an atlas is also a valid way to speed up rendering. You can do it by constructing TCastleScene from multiple shapes that refer to different pieces of your texture.

In practice, it is simplest to do it by just using Tiled integration, Tiled maps | Manual | Castle Game Engine . Using tilesets means you have an atlas, and the Tiled integration automatically sets up nodes in a way that it is very efficient to render even huge maps.

This is also independent from memory optimization and using batching .

To repeat what I said at the beginning: Make sure what you want to optimize, and make sure it is necessary :slight_smile: We mentioned a few techniques in this thread, they achieve different things in regards to time and memory. In simple cases, you may not need any of them, and have things simple to code. So to summarize, we have on the table:

  • Doing nothing :slight_smile: Textures are cached automatically. Having multiple TCastleImageTransform instances with same URL is not memory-heavy. See examples/isometric_demo .

  • Using multiple times the same TCastleTransform (or any descendant, like TCastleScene, TCastleImageTransform) instance. This in practice implies you need to create multiple instances of (exactly) TCastleTransform and inside them place the same reference to a “heavier” object like TCastleScene.

  • Using TCastleTransformReference. This is similar to above.

  • Using dynamic batching (not to optimize memory, but to speed up rendering).

  • Using atlas to have a common image (“atlas”) for all pieces. May conserve both time and memory.

  • Using Tiled, which in practice means you have atlas and batching automatically.

1 Like

P.S. There are more ideas on Optimization and profiling | Manual | Castle Game Engine . With a similar theme:

  • Measure things (speed, memory usage).

  • Make sure you really need to optimize, do not optimize when not need. All optimization techniques imply that using something becomes less comfortable or even may block from adding some features. Or make adding some features super-complicated. Do not optimize if you don’t need to.

  • If you go with some optimization, either for time or memory, measure the effects. If the optimization didn’t really achieve anything measurable, absolutely “roll it back”. You don’t want complicated optimizations that don’t yield any measurable benefit for users, but block you from implementing new features.

  • If you find a specific case that is hard to optimize, you’re welcome to send a testcase. It may be easy to point to optimization once we see a precise testcase. I can also optimize a lot of things on the engine side – we have various plans, there are areas to optimize better (for time or memory) on the engine side. We implement them as we go :slight_smile:

1 Like