Autor Beitrag
glotzer
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 07:33 
Hallo alle zusammen,

es geht um ein Tower Defense Spiel welches ich zurzeit entwickle.
Das Spielfeld dabei basiert auf Quadratischen Feldern, welche irgendwie verwaltet werden müssen.
Mein Problem ist ,dass meine jetzige Lösung (ist auch schon die 3. Version davon) bei größeren Spielfeldern (<50x50) zu langsam wird.
Zuerst hab ich ein XML-Datenformat probiert. Mit > 50mb Dateigröße, Ramverbrauch von < 500mb war das allerdings nicht verwendbar.
Danach bin ich zu einem Binären Format mit TFileStream übergegangen. Das war mir allerdings noch zu langsam, weshalb ich dann zu TFastFileStream von Flamefire gekommen bin.
Damit hat sich das ganze schon spürbar verbessert ist aber immernoch zu langsam.

Ich hab ziemlich sicher irgendwo einen kleinen Fehler drinnen der sehr viel Zeit kostet. Nur damit ihr eine Vorstellung davon bekommt wie langsam das ganze ist:
das Erstellen und Speichern einer Karte mit 300x300 Feldern dauert etwas über 2 Minuten. Das Laden hingegen geht mit etwa 10s noch halbwegs schnell.

Ideen dich ich noch hätte:
-Multi threading: wäre wahrscheinlich möglich, allerdings ein ziemlich großer Aufwand, da muss es doch was besseres geben.
-Aufteilen auf kleine (20x20?) große blöcke die nacheinander geladen werden wenn man schon ingame ist: ziemlich schwer umzusetzen und bei einem TD Spiel meiner Meinung nach auch unpraktisch da man eigentlich die gesamte Karte braucht bevor man irgendetwas machen kann.



Ist jemand so nett und könnte da mal drüber schauen? Tausend Dank im Voraus :D

Grüße
glotzer


Die Datenverwaltungsklasse:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
unit MapData;

interface

uses
  MapMaterial, Generics.Collections, SysUtils, classes, StreamUtils, unFastFileStream, irrlicht,
  TypInfo;

type
  TFieldType = (FT_BuildableGround=0,FT_Blocked=1, FT_Tower=3);
  TFieldSpecial = set of (FS_Spawn,FS_Waypoint,FS_Despawn);
  TFieldData = record
    private
      {..}
    private
      FX,FY: Integer;
      FFieldType: TFieldType;
      FAdditionalInfo: variant;
      FMaterialID: String;
      FSpecialFunction: TFieldSpecial;
    public
      property X: integer read FX write SetX;
      property Y: integer read FY write SetY;
      property FieldType: TFieldType read FFieldType write SetFieldType;
      property AdditionalInfo: variant read FAdditionalInfo write SetAdditionalInfo;
      property MaterialID: String read FMaterialID write SetMaterialID;
      property Material: TMapMaterial read GetMaterial write SetMaterial;
      property SpecialFunction: TFieldSpecial read FSpecialFunction write SetSpecialFunction;
    public
      procedure WriteToStream(stream: TStream);
      procedure LoadFromStream(stream: TStream);
  end;

type
  TMapData = class
    protected
      FWidth: Integer;
      FHeight: Integer;
      FFields: TList<TFieldData>;
    protected
      function FindField(AX,AY:Integer):integer;
      function GetFields(AX,AY:Integer): TFieldData;
      procedure SetFields(AX,AY:Integer; const Value: TFieldData);
      {..}
    public
      constructor Create;
      destructor Destroy; override;
    public
      procedure Load(FileName: String);
      procedure Save(FileName: String);
      procedure SortFields;
      procedure Clear;
      procedure Resize(AW,AH: integer);
    public
      property Width: Integer read FWidth;
      property Height: Integer read FHeight;
      property Fields[AX,AY:Integer]: TFieldData read GetFields write SetFields;
  end;

implementation

uses
  GameUtils;


procedure TFieldData.LoadFromStream(stream: TStream);
var
  t: byte;
begin
  x := ReadUInt64FromStream(stream);
  y := ReadUInt64FromStream(stream);
  FieldType := TFieldType(ReadUInt64FromStream(stream));
  MaterialID := ReadStringFromStream(Stream);
  t := StringToSet(PTypeInfo(TypeInfo(TFieldSpecial)),ReadStringFromStream(stream));
  SpecialFunction := TFieldSpecial(t);
end;

procedure TFieldData.WriteToStream(stream: TStream);
begin
  WriteUInt64ToStream(stream,x);
  WriteUInt64ToStream(stream,y);
  WriteUInt64ToStream(stream,integer(FieldType));
  WriteStringToStream(Stream,MaterialID);
  WriteStringToStream(Stream,SetToString(PTypeInfo(TypeInfo(TFieldSpecial)),Byte(SpecialFunction),true));
end;

{..}

{ TMapData }

procedure TMapData.Clear;
begin
  FFields.Clear;
end;

constructor TMapData.Create;
begin
  FFields := TList<TFieldData>.Create;
end;

destructor TMapData.Destroy;
begin
  FFields.Free;
  inherited;
end;

function TMapData.FindField(AX, AY: Integer): integer;
var
  I: Integer;
  found: boolean;
begin
  try
    result := AY+AX*(Height);
  except
    raise Exception.Create('Map field at position x='+inttostr(AX)+' and y='+inttostr(AY)+' was not found');
  end;

  if (FFields.Items[result].X <> AX) or (FFields.Items[result].Y <> AY) then
  begin
    found := false;
    logger.log(PChar('TMapData.FindField('+inttostr(AX)+','+inttostr(AY)+'); had to use fallback slow method'));
    //slow!
    for I := 0 to FFields.Count-1 do
    begin
      if (FFields.Items[i].X = AX) and (FFields.Items[i].Y = AY) then
      begin
        result := i;
        found := true;
        break;
      end;
    end;
    if not found then raise Exception.Create('Map field at position x='+inttostr(AX)+' and y='+inttostr(AY)+' was not found');
  end;
end;

procedure TMapData.Resize(AW, AH: integer);
var
  x: Integer;
  y: Integer;
  tmp: TFieldData;
begin
  Clear;
  tmp.FieldType := FT_Blocked;
  tmp.MaterialID := EMPTY_MAP_MATERIAL;
  tmp.SpecialFunction := [];
  for x := 0 to AW-1 do
  begin
    for y := 0 to AH-1 do
    begin
      tmp.X := x;
      tmp.Y := y;
      FFields.Add(tmp);
    end;
  end;
  FWidth := AW;
  FHeight := AH;
end;

procedure TMapData.Load(FileName: String);
var
  Stream: TFastFileStream;
  FileVersion: UInt64;
  
  procedure ReadSize;
  var
    w,h: UInt64;
  begin
    ReadUInt64FromStream(stream,w);
    ReadUInt64FromStream(stream,h);
    Resize(w,h);
  end;
  
  procedure ReadFields;
  var
    x: Integer;
    y: Integer;
    ReportEach,FieldsReaded: integer;
  begin
    ReportEach := ((width-1)*(height-1)) div 20;
    if ReportEach > 15000 then ReportEach := 15000;
    
    FieldsReaded := 0;
    //now Read all fields
    for x := 0 to width-1 do
    begin
      for y := 0 to height-1 do
      begin
        Fields[x,y].LoadFromStream(Stream);
        inc(FieldsReaded);
        if (FieldsReaded mod ReportEach) = 0 then logger.log(PChar('TMapData.Load -> ReadFields is at '+inttostr(FieldsReaded)+'/'+inttostr(((width-1)*(height-1)))+' fields thats '+inttostr( round(FieldsReaded/((width-1)*(height-1))*100) )+'%'));        
      end;
    end;
  end;
  
begin
  Stream := TFastFileStream.Create(FileName,fmOpenRead);
  try
    //Test magic word
    if ReadStringFromStream(stream) <> MapFileMagicWord then
    begin
      raise Exception.Create('File "'+FileName+'" is not a valide map file.');
    end;
    //Read file version
    ReadUInt64FromStream(stream,FileVersion);

    //check file size
    if ReadUInt64FromStream(stream) <> stream.Size then
    begin
      raise Exception.Create('File size is wrong, the map file "'+FileName+'" is corrupted.');
    end;
    
    case FileVersion of
      MapFileVersion_Cur: 
        begin
          ReadSize;
          ReadFields;
        end;
    end;
  finally
    Stream.Free;
  end;
end;

procedure TMapData.Save(FileName: String);
var
  x: Integer;
  y: Integer;
  Stream: TFastFileStream;
  SizePos: Int64;
  tmp: AnsiString;
  FieldsWritten,ReportEach: Integer;
begin
  Stream := TFastFileStream.Create(FileName,fmCreate);
  try
    //Write magic word
    WriteStringToStream(stream,MapFileMagicWord);
    //Write file version
    WriteUInt64ToStream(stream,MapFileVersion_Cur);
    //Reserve space for file size
    SizePos := Stream.Position;
    WriteUInt64ToStream(stream,0);

    //Write width
    WriteUInt64ToStream(stream,width);
    //Write height
    WriteUInt64ToStream(stream,height);

    ReportEach := ((width-1)*(height-1)) div 20;
    if ReportEach > 15000 then ReportEach := 15000;
    FieldsWritten := 0;
    //now write all fields
    for x := 0 to width-1 do
    begin
      for y := 0 to height-1 do
      begin
        Fields[x,y].WriteToStream(Stream);
        inc(FieldsWritten);
        if (FieldsWritten mod ReportEach) = 0 then logger.log(PChar('TMapData.Save is at '+inttostr(FieldsWritten)+'/'+inttostr(((width-1)*(height-1)))+' fields thats '+inttostr( round(FieldsWritten/((width-1)*(height-1))*100) )+'%'));        
      end;
    end;
    //now correct the file Size
    Stream.Position := SizePos;
    WriteUInt64ToStream(stream,Stream.Size);
  finally
    Stream.Free;
  end;
end;


function TMapData.GetFields(AX, AY: Integer): TFieldData;
begin
    result := FFields.Items[FindField(AX, AY)];
end;

procedure TMapData.SetFields(AX, AY: Integer; const Value: TFieldData);
begin
  FFields.Items[FindField(AX, AY)] := Value;
end;

{..}

procedure TMapData.SortFields;
var
  newList:  TList<TFieldData>;
  tmp: TFieldData;
  I: Integer;
begin
  newList :=   TList<TFieldData>.Create;
  for I := 0 to FFields.Count-1 do
  begin
    newList.Add(tmp);
  end;
  for I := 0 to FFields.Count-1 do
  begin
    newList.Items[FFields.Items[i].Y+FFields.Items[i].X*(Height)] := FFields.Items[i];
  end;
end;

end.


Hilfsunits:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
unit StreamUtils;

interface

uses
  classes;

const
  MapFileMagicWord: AnsiString = 'I-like-potatos-and-this-is-a-DelphiTD-Map-File';
  MapFileVersion_Cur = 1;

procedure WriteStringToStream(s: TStream; str: AnsiString);
procedure WriteUInt64ToStream(s: TStream; int: UInt64);

procedure ReadStringFromStream(s: TStream; var str: AnsiString); overload;
function ReadStringFromStream(s: TStream): AnsiString; overload;
procedure ReadUInt64FromStream(s: TStream; var int: UInt64); overload;
function ReadUInt64FromStream(s: TStream): UInt64; overload;

implementation

procedure WriteStringToStream(s: TStream; str: AnsiString);
var
  l: UInt64;
begin
  l := length(str);
  s.Write(l,SizeOf(l));
  s.Write(Pointer(str)^,l);
end;

procedure WriteUInt64ToStream(s: TStream; int: UInt64);
begin
  s.Write(int,SizeOf(int));
end;

procedure ReadStringFromStream(s: TStream; var str: AnsiString);
var
  l: UInt64;
begin
  s.Read(l,SizeOf(l));
  SetLength(str,l);
  s.Read(Pointer(str)^,l);
end;

function ReadStringFromStream(s: TStream): AnsiString;
begin
  ReadStringFromStream(s,result);
end;

procedure ReadUInt64FromStream(s: TStream; var int: UInt64);
begin
  s.Read(int,SizeOf(int));
end;

function ReadUInt64FromStream(s: TStream): UInt64;
begin
  ReadUInt64FromStream(s,result);
end;


end.


unFastFileStream (hier im Forum vorgestellt)

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 03.09.12 09:22 
Hallo,

aus welchem Grund loggst du denn beim Speichern 20-mal innerhalb der Schleife? Ich denke dies wird u.a. die Performance-Bremse sein.

Und warum speicherst du das Set FSpecialFunction als String? Du hast es doch schon explizit in ein byte gecastet, dann speichere es doch auch so ab - dies dürfte noch mal ein bißchen Performance (und Reduzierung der Dateigröße) bringen.
Beachte, daß du bei einem Byte nur max. 8 Werte in dem Set haben darfst, evtl. nimm daher besser generell ein Integer (max. 32 verschiedene Werte).
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 09:44 
Danke für die Tipps,
allerdings bezweifle ich ziemlich das dieser 20x geloggte String ein großes Problem ist, er wird später mit einem Ladebalken ersetzt.
Werde ich allerdings ausprobieren.

Der Grund warum ich das Set als String speicher ist hauptsächlich damit die Karte bei neueren Programmversionen (und eventuell erweiterten/geänderten Set) immernoch die alten
Karten laden kann. Werd ich auch mal probieren als Integer zu speichern.

Aber ich glaub nicht dass das wirklich sooooo große Bremsen sind.

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 10:03 
vorher ohne Änderung: 83881ms = 83,8s
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
0107906765 ELL_INFORMATION begin
0107911944 ELL_INFORMATION TMapData.Save is at 4470/89401 fields thats 5%
0107915813 ELL_INFORMATION TMapData.Save is at 8940/89401 fields thats 10%
0107919760 ELL_INFORMATION TMapData.Save is at 13410/89401 fields thats 15%
0107923722 ELL_INFORMATION TMapData.Save is at 17880/89401 fields thats 20%
0107928246 ELL_INFORMATION TMapData.Save is at 22350/89401 fields thats 25%
0107932130 ELL_INFORMATION TMapData.Save is at 26820/89401 fields thats 30%
0107936062 ELL_INFORMATION TMapData.Save is at 31290/89401 fields thats 35%
0107939977 ELL_INFORMATION TMapData.Save is at 35760/89401 fields thats 40%
0107943831 ELL_INFORMATION TMapData.Save is at 40230/89401 fields thats 45%
0107947731 ELL_INFORMATION TMapData.Save is at 44700/89401 fields thats 50%
0107951677 ELL_INFORMATION TMapData.Save is at 49170/89401 fields thats 55%
0107956186 ELL_INFORMATION TMapData.Save is at 53640/89401 fields thats 60%
0107960164 ELL_INFORMATION TMapData.Save is at 58110/89401 fields thats 65%
0107964157 ELL_INFORMATION TMapData.Save is at 62580/89401 fields thats 70%
0107968182 ELL_INFORMATION TMapData.Save is at 67050/89401 fields thats 75%
0107972940 ELL_INFORMATION TMapData.Save is at 71520/89401 fields thats 80%
0107977012 ELL_INFORMATION TMapData.Save is at 75990/89401 fields thats 85%
0107981240 ELL_INFORMATION TMapData.Save is at 80460/89401 fields thats 90%
0107985342 ELL_INFORMATION TMapData.Save is at 84930/89401 fields thats 95%
0107989632 ELL_INFORMATION TMapData.Save is at 89400/89401 fields thats 100%
0107990646 ELL_INFORMATION end


ohne Log Message: 80574ms = 80,5s also 3,3s weniger
ausblenden Quelltext
1:
2:
0108108287 ELL_INFORMATION begin
0108188861 ELL_INFORMATION end


Set als Integer gespeichert: 70170ms = 70,1s also 13,7s weniger
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
0108355548 ELL_INFORMATION begin
0108358918 ELL_INFORMATION TMapData.Save is at 4470/89401 fields thats 5%
0108362849 ELL_INFORMATION TMapData.Save is at 8940/89401 fields thats 10%
0108366141 ELL_INFORMATION TMapData.Save is at 13410/89401 fields thats 15%
0108369495 ELL_INFORMATION TMapData.Save is at 17880/89401 fields thats 20%
0108372896 ELL_INFORMATION TMapData.Save is at 22350/89401 fields thats 25%
0108376718 ELL_INFORMATION TMapData.Save is at 26820/89401 fields thats 30%
0108380150 ELL_INFORMATION TMapData.Save is at 31290/89401 fields thats 35%
0108383551 ELL_INFORMATION TMapData.Save is at 35760/89401 fields thats 40%
0108386827 ELL_INFORMATION TMapData.Save is at 40230/89401 fields thats 45%
0108390243 ELL_INFORMATION TMapData.Save is at 44700/89401 fields thats 50%
0108394190 ELL_INFORMATION TMapData.Save is at 49170/89401 fields thats 55%
0108397528 ELL_INFORMATION TMapData.Save is at 53640/89401 fields thats 60%
0108400882 ELL_INFORMATION TMapData.Save is at 58110/89401 fields thats 65%
0108404283 ELL_INFORMATION TMapData.Save is at 62580/89401 fields thats 70%
0108407621 ELL_INFORMATION TMapData.Save is at 67050/89401 fields thats 75%
0108410976 ELL_INFORMATION TMapData.Save is at 71520/89401 fields thats 80%
0108414408 ELL_INFORMATION TMapData.Save is at 75990/89401 fields thats 85%
0108418276 ELL_INFORMATION TMapData.Save is at 80460/89401 fields thats 90%
0108421568 ELL_INFORMATION TMapData.Save is at 84930/89401 fields thats 95%
0108425265 ELL_INFORMATION TMapData.Save is at 89400/89401 fields thats 100%
0108425718 ELL_INFORMATION end


Beides: 69982ms = 69,9s also 13,9s weniger
ausblenden Quelltext
1:
2:
0108545651 ELL_INFORMATION begin
0108615633 ELL_INFORMATION end


also im besten Fall 16% schneller :D immerhin schonmal etwas aber da muss doch wohl noch mehr gehn

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 03.09.12 10:29 
Hallo,

hast du schon mal im TaskManager geschaut, ob nicht ein anderer Prozess dir Rechenleistung wegnimmt? Und wie groß ist denn dann deine Datei (nachgerechnet komme ich auf ca. 3 MB, sofern MaterialID nur ein kurzer String ist)? Und dies sollte wirklich in ein paar Sekunden geschrieben sein.

Btw.: Du speicherst String immer mit einem UInt64 als Länge (obwohl AnsiString intern nur einen Int32 als Länge benutzt), s. docwiki.embarcadero....ing_Types#AnsiString
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 10:36 
Ne es läuft nur mein Programm sonst nix. Die Datei ist am Ende 4 mb groß.
Hab grad noch ein bischen mit inline rumprobiert, aber ich komm nicht wesentlich unter 70s.

Das mit Int32 werd ich gleich mal probieren :D

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 10:44 
Alle uint64 durch LongWord ersetzt, keine wesentliche Änderung ;(

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 03.09.12 11:16 
Jetzt erst sehe ich, daß du bei Fields[x, y] intern ja jedesmal FindField aufrufst (und da steht doch als Kommentar "slow!" dabei). Hast du diese Funktion denn selber geschrieben? Oder springt er gar nicht in die Bedingung rein, weil sonst würde ja auch der Logger etwas anzeigen?
Ich dachte, du hättest ein 2 dimensionales Feld und würdest dieses direkt wegschreiben...
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 11:35 
Ja die ist selber geschrieben. Er springt normalerweiße nie in die Bedingung rein. Das ist mehr so als "Notsystem" gedacht wenn irgendwas sehr kaputt ist.
Zuerst hatte ich auch eine 2 Dimensionale Array, aber so sollte es doch nicht wesentlich langsamer sein.
Alles was er da rechnet ist ja result := AY+AX*(Height)

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 03.09.12 13:37 
Dann solltest du mal einen Profiler verwenden, um die Performance zu messen - ich sehe sonst nichts weiter verdächtiges (ich selber habe gerade mal eine ähnliche Methode mit C# entwickelt - und diese braucht für die doppelte Schleife mit je 300 Werten keine Sekunde, um eine ca. 2 MB große Datei zu erzeugen).

Hast du denn bisher die Tests im Release-Modus laufen lassen?
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Mo 03.09.12 13:58 
Bisherige Tests waren alle im Debug Modus, gerade nochmal im Release-Modus getestet, kein wesentlicher Unterschied.

Sollte ich AQTime noch soweit kriegen das es mein Programm startet, dann kommt der Profiling Bericht noch.

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Blup
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 173
Erhaltene Danke: 43



BeitragVerfasst: Mo 03.09.12 14:14 
Warum wird für jedes Field x,y gespeichert? Diese Information ist doch schon aus der Reihenfolge der Daten bekannt und durch "Resize" beim Laden vorbelegt.

Im "Resize" unbedingt vorab "FFields.Capacity" setzen.

ausblenden Delphi-Quelltext
1:
FMaterialID: String;					

Wie viele verschiedene Materialien kommen den in einer Map vor?
Genügt hier nicht ein Integer, der als Index in einer StringList/Map mit der eigentlichen Materialbezeichnung dient? Diese Materialbibliothek könnte man auch in der Datei ablegen.

Vieleicht solltest du das Lesen und Speichern nicht im Datenobjekt selbst realisieren, sondern dafür das Visitor-Muster einsetzen. Das würde zumindest das Lesen aus verschiedenen (z.B. älteren) Dateiformaten erleichtern.

Damit die Daten gut komprimiert werden können ist es sinnvoll nicht jede Zelle einzeln zu speichern, sondern gleichartige Daten zusammen zu fassen.
Aus dem Kopf natürlich ungetestet nur als Anregung:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
procedure TMySaveVisitorFileformatXXX.Visit(AVisited: TMapData);
begin
  FStream := TFastFileStream.Create(FileName,fmCreate);
  try
    SaveHeader(AVisited);
    SaveMaterialBibliothek(AVisited);
    SaveMaterial(AVisited);
    SaveFieldType(AVisited);
    {...}
  finally
    FStream.Free;
  end;
end;

procedure TMySaveVisitorFileformatXXX.SaveFieldType(AVisited: TMapData);
var
  FBuffer: PByte;
  cnt, i1, i2: Integer;
  t: TFieldType;
begin
  cnt := AVisited.Height * AVisited.Width; 
  GetMem(FBuffer, cnt);
  try
    {zuerst das untere Byte jedes Typs speichern
     00000001, 00000005, 00000002
     wird zu 
     010502, 000000, 000000, 000000}

    for i1 := 0 to SizeOf(t) - 1 do
    begin
      for i2 := 0 to cnt - 1 do
      begin
        t := AVisited.FFields[i2].FieldType;
        FBuffer[i2] := PByte(@t)[i1];
      end;
      FStream.Write(FBuffer^, cnt);
    end;
  finally
    FreeMem(FBuffer);
  end;
end;
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Mo 03.09.12 15:00 
Ja, speichern von Tilemaps. Ich kann mich da an was erinnern: 50x50 waren 600k, das war zu groß um das sinnvoll übers Netzwerk zu schieben... :roll:

Grundsätzlich ist mir nicht ganz klar, warum das bei dir so langsam ist, aber dass die Dateien sinnlos groß sind ist eindeutig. Du speicherst massenweise abgeleitete Daten (X,Y) oder ungeeignete Typen (das set als String). Das ist zwar groß, aber nicht an sich langsam, und TFastFileStream ist ja immerhin buffered.

ausblenden Delphi-Quelltext
1:
  WriteUInt64ToStream(stream,integer(FieldType));					

Rollen sich dir da nicht die Zehennägel auf? Ein Byte, gecastet auf ein Integer, wird geschrieben als Int64 :shock:

Reine Nutzdaten hast du 6 Byte pro Feld: TFieldType(byte), TFieldSpecial(Byte, eventuell mal später ein Word), MaterialID (sollte ein DWORD ja wohl mehr als ausreichen). Das sind 15kByte für ein 50x50-Feld.

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Di 04.09.12 00:50 
Erstmal vielen Dank dass ihr euch die Mühe macht euch das Ganze anzuschauen :D

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Warum wird für jedes Field x,y gespeichert? Diese Information ist doch schon aus der Reihenfolge der Daten bekannt und durch "Resize" beim Laden vorbelegt.

Stimmt, das ist ein bischen doof. Gerade geändert.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:

Im "Resize" unbedingt vorab "FFields.Capacity" setzen.

was meinst du damit?

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
]
Wie viele verschiedene Materialien kommen den in einer Map vor?
Genügt hier nicht ein Integer, der als Index in einer StringList/Map mit der eigentlichen Materialbezeichnung dient? Diese Materialbibliothek könnte man auch in der Datei ablegen.

Das geht so leider nicht, der Grund ist ein bischen komplizierter: Es gibt ein paar Materialien die beim Programm dabei sind, die werden über "lib.[untergruppe].[materialname]" angesprochen.
Zusätzlich kann man aber auch eigene Materialien mit der Karte mitliefern, oder Materialien anderer Karten verwenden. Das geht dann mit "[mapname].[untergruppe].[materialname]"
Ein einfacher Integer wird sowas leider nicht können. Die idee am Anfang der Datei eine Liste mit allen möglichen Materialien anzulegen und dann nur Indexes zu verwenden ist allerdings sehr gut, das werd ich mal einbauen.


user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
]
Vieleicht solltest du das Lesen und Speichern nicht im Datenobjekt selbst realisieren, sondern dafür das Visitor-Muster einsetzen. Das würde zumindest das Lesen aus verschiedenen (z.B. älteren) Dateiformaten erleichtern.

Damit die Daten gut komprimiert werden können ist es sinnvoll nicht jede Zelle einzeln zu speichern, sondern gleichartige Daten zusammen zu fassen.
Aus dem Kopf natürlich ungetestet nur als Anregung:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
procedure TMySaveVisitorFileformatXXX.Visit(AVisited: TMapData);
begin
  FStream := TFastFileStream.Create(FileName,fmCreate);
  try
    SaveHeader(AVisited);
    SaveMaterialBibliothek(AVisited);
    SaveMaterial(AVisited);
    SaveFieldType(AVisited);
    {...}
  finally
    FStream.Free;
  end;
end;

procedure TMySaveVisitorFileformatXXX.SaveFieldType(AVisited: TMapData);
var
  FBuffer: PByte;
  cnt, i1, i2: Integer;
  t: TFieldType;
begin
  cnt := AVisited.Height * AVisited.Width; 
  GetMem(FBuffer, cnt);
  try
    {zuerst das untere Byte jedes Typs speichern
     00000001, 00000005, 00000002
     wird zu 
     010502, 000000, 000000, 000000}

    for i1 := 0 to SizeOf(t) - 1 do
    begin
      for i2 := 0 to cnt - 1 do
      begin
        t := AVisited.FFields[i2].FieldType;
        FBuffer[i2] := PByte(@t)[i1];
      end;
      FStream.Write(FBuffer^, cnt);
    end;
  finally
    FreeMem(FBuffer);
  end;
end;

Das erste verstehe ich, sowas ähnliches werd ich noch machen. Das procedure TMySaveVisitorFileformatXXX.SaveFieldType(AVisited: TMapData); hingegen sagt mir garnix. Könntest du das etwas näher erklären?



user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:

ausblenden Delphi-Quelltext
1:
  WriteUInt64ToStream(stream,integer(FieldType));					

Rollen sich dir da nicht die Zehennägel auf? Ein Byte, gecastet auf ein Integer, wird geschrieben als Int64 :shock:

Joa ok, dass ist wirklich nicht so ganz das wahre, daraus hab ich jetzt WriteLongWordToStream(stream,LongWord((@SpecialFunction)^)); gemacht. Gibt es da vieleicht einen besseren Weg als das pointergehample?


Folgendes sehr überraschendes Ergebnis von AQtime:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
Routine Name                          Time                  Time with Children  Shared Time          Hit Count
TFastFileStream::Seek      0,090720801286644  44,5660133473541  0,203564991509455  1080025
TFastFileStream::ReInitView    0,18515186178954  44,4753132990927  0,416302546413578  1080022
Winapi::Windows::MapViewOfFile    1,26992679521008  1,26992679521008  100      1080020
Winapi::Windows::UnmapViewOfFile  2,84620935833247  2,84620935833247  100      1080020
Winapi::Windows::FlushViewOfFile  40,1742471224666  40,1742471224666  100      1080019
TFastFileStream::Write  0,724547790927435  45,3159514128609  1,59888023607026  360007

kann es sein dass es da einen Bug im TFastFileStream gibt?


edit: mit allen Verbesserungen bin ich jetzt bei 35s, immerhin schon mal die Hälfte vom Anfang. Neuer Code kommt gleich

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
procedure TMapData.Load(FileName: String);
var
  Stream: TFastFileStream;
  FileVersion: LongWord;
  Materials: TStringList;
  
  procedure ReadFields;
  var
    ReportEach,FieldsReaded: integer;
    x: Integer;
    y: Integer;
    tmp: LongWord;
  begin
    ReportEach := ((width-1)*(height-1)) div 20;
    if ReportEach > 15000 then ReportEach := 15000;
    
    FieldsReaded := 0;
    //now Read all fields
    for x := 0 to width-1 do
    begin
      for y := 0 to height-1 do
      begin
        //-------------------
        Fields[x,y].FieldType := TFieldType(ReadLongWordFromStream(stream));
        Fields[x,y].MaterialID := Materials.Strings[ReadLongWordFromStream(stream)];
        ReadLongWordFromStream(stream,tmp);
        Fields[x,y].SpecialFunction := TFieldSpecial((@tmp)^);
        Fields[x,y].AdditionalInfo := ReadLongWordFromStream(stream);
        //-------------------
        inc(FieldsReaded);
        if (FieldsReaded mod ReportEach) = 0 then
        begin
          if assigned(self.OnProress) then
          begin
            self.OnProress(FFields.Count-1,FieldsReaded);
          end;
        end;
      end;
    end;
  end;

  Procedure ReadHeader;
  var
    w,h: LongWord;
  begin
    //Test magic word
    if ReadStringFromStream(stream) <> MapFileMagicWord then
    begin
      raise Exception.Create('File "'+FileName+'" is not a valide map file.');
    end;
    //Read file version
    ReadLongWordFromStream(stream,FileVersion);

    //check file size
    if ReadLongWordFromStream(stream) <> stream.Size then
    begin
      raise Exception.Create('File size is wrong, the map file "'+FileName+'" is corrupted.');
    end;

    //set size
    ReadLongWordFromStream(stream,w);
    ReadLongWordFromStream(stream,h);
    Resize(w,h);
  end;

  procedure ReadMaterialList;
  var
    OldPos: integer;
    FieldListSize,MaterialListCount: LongWord;
    I: Integer;
  begin
    //Read Field list size
    ReadLongWordFromStream(stream,FieldListSize);
    OldPos := stream.position;
    //skip it
    stream.position := stream.position + FieldListSize;

    //read the material list
    ReadLongWordFromStream(stream,MaterialListCount);
    for I := 0 to MaterialListCount do
    begin
      Materials.Add(ReadStringFromStream(stream));
    end;
    //back to the fields
    stream.position := OldPos;
  end;
  
begin
  Stream := TFastFileStream.Create(FileName,fmOpenRead);
  Materials := TStringList.Create;
  Materials.Duplicates := dupIgnore;
  Materials.Sorted := true;
  Materials.CaseSensitive := true;
  try
    ReadHeader;

    case FileVersion of
      MapFileVersion_Cur:
        begin
          ReadMaterialList;
          ReadFields;
        end;
      else
      begin
        if assigned(logger) then
        begin
          logger.log(pChar('Unknown map file version '+inttostr(FileVersion)+' ...terminating'),ELL_ERROR);
        end;
        halt;
      end;
    end;

  finally
    Materials.Free;
    Stream.Free;
  end;
end;

procedure TMapData.Save(FileName: String);
var
  Stream: TFastFileStream;
  FileSizePos: Int64;
  Materials: TStringList;

  procedure SaveHeader;
  begin
    //Write magic word
    WriteStringToStream(stream,MapFileMagicWord);
    //Write file version
    WriteLongWordToStream(stream,MapFileVersion_Cur);
    //Reserve space for file size
    FileSizePos := Stream.Position;
    WriteLongWordToStream(stream,0);
    //Write width
    WriteLongWordToStream(stream,width);
    //Write height
    WriteLongWordToStream(stream,height);
    //Write field section size
    WriteLongWordToStream(stream,SizeOf(LongWord)*3*FFields.Count);
  end;

  procedure SaveFields;
  var
    x: Integer;
    y: Integer;
    FieldsWritten,ReportEach: Integer;
    tmp : TFieldSpecial;
  begin
    ReportEach := ((width-1)*(height-1)) div 20;
    if ReportEach > 15000 then ReportEach := 15000;
    FieldsWritten := 0;
    for x := 0 to width-1 do
    begin
      for y := 0 to height-1 do
      begin
        //-------------------
        WriteLongWordToStream(stream,integer(Fields[x,y].FieldType));
        WriteLongWordToStream(stream,Materials.Add(Fields[x,y].MaterialID));
        tmp := Fields[x,y].SpecialFunction;
        WriteLongWordToStream(stream,LongWord((@tmp)^));
        WriteLongWordToStream(stream,Fields[x,y].AdditionalInfo);
        //-------------------
        inc(FieldsWritten);
        if (FieldsWritten mod ReportEach) = 0 then
        begin
          if assigned(self.OnProress) then
          begin
            self.OnProress(FFields.Count-1,FieldsWritten);
          end;
        end;
      end;
    end;
  end;

  procedure SaveMaterialList;
  var
    I: Integer;
  begin
    //Write materials section count
    WriteLongWordToStream(stream,Materials.Count);
    for I := 0 to Materials.Count-1 do
    begin
      WriteStringToStream(stream,Materials.Strings[i]);
    end;
  end;
begin
  Stream := TFastFileStream.Create(FileName,fmCreate);
  Materials := TStringList.Create;
  Materials.Duplicates := dupIgnore;
  Materials.Sorted := true;
  Materials.CaseSensitive := true;
  try
    SaveHeader;
    SaveFields;
    SaveMaterialList;

    //now correct the file Size
    Stream.Position := FileSizePos;
    WriteLongWordToStream(stream,Stream.Size);
    Stream.Position := Stream.Size;
  finally
    Materials.Free;
    Stream.Free;
  end;
end;


Meine vermutung ist das der FastFileStream die Bremse ist. Ich werde später heute noch einen eigenen Stream schreiben der den Puffer im speicher vorhält, also quasi ein TMemoryStream, nur dass er in 5(?)mb Blöcken vergrößert wird und nicht bei jenen write Aufruf.

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
Blup
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 173
Erhaltene Danke: 43



BeitragVerfasst: Di 04.09.12 10:12 
user profile iconglotzer hat folgendes geschrieben Zum zitierten Posting springen:

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:

Im "Resize" unbedingt vorab "FFields.Capacity" setzen.

was meinst du damit?

Vor dem Hinzufügen neuer Elemente bekanntgeben, wie viel Speicher die Liste insgesamt benötigt.
Dazu bitte die Hilfe konsultieren: TList.Capacity

user profile iconglotzer hat folgendes geschrieben Zum zitierten Posting springen:

Das geht so leider nicht, der Grund ist ein bischen komplizierter: Es gibt ein paar Materialien die beim Programm dabei sind, die werden über "lib.[untergruppe].[materialname]" angesprochen.
Zusätzlich kann man aber auch eigene Materialien mit der Karte mitliefern, oder Materialien anderer Karten verwenden. Das geht dann mit "[mapname].[untergruppe].[materialname]"

Wenn man zuerst eine Liste mit den Namen der Materialien speichert:
"lib.[untergruppe].[materialname]"
"lib.[untergruppe].[materialname]"
"[mapname].[untergruppe].[materialname]"
...
genügt es für jede einzelne Kachel den Index in dieser Liste zu speichern.

user profile iconglotzer hat folgendes geschrieben Zum zitierten Posting springen:
Das procedure TMySaveVisitorFileformatXXX.SaveFieldType(AVisited: TMapData); hingegen sagt mir garnix. Könntest du das etwas näher erklären?

Bei den meisten Integerwerten ebenso wie bei Aufzählungstypen(sind auch nur Integer) ist meist nur das unteren Byte belegt.
Speichere ich in der Datei Kachel für Kachel so liegt dort zuerst eine MaterialID, dann ein FieldTyp usw. Bei der nächsten Kachel geht das ganze von vorn los. Solche Daten lassen sich relativ schlecht komprimieren.

1. Schritt
Man speichert alle gleichartigen Daten in einem Block, zuerst die MaterialID aller Felder, dann den Feldtyp aller Felder usw.
Beispiel:
FT_Blocked, FT_Blocked, FT_Blocked, FT_BuildableGround, FT_BuildableGround, FT_Tower
01 00 00 00, 01 00 00 00, 01 00 00 00, 00 00 00 00, 00 00 00 00, 02 00 00 00

2. Schritt
Innerhalb eines Blocks sortiert man die Byte um, damit gleichwertige Byte hintereinander liegen, hier am Beispiel FieldTyp
Zuerst das untere Byte aller FieldTyp Felder, dann das nächst höhere, usw.
Beispiel:
01 01 01 00 00 02, 00 00 00 00 00, 00 00 00 00 00, 00 00 00 00 00

Die Daten lassen sich so viel besser komprimieren und entsprechend schneller laden.
glotzer Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 393
Erhaltene Danke: 49

Win 7
Lazarus
BeitragVerfasst: Di 04.09.12 13:49 
user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:

Vor dem Hinzufügen neuer Elemente bekanntgeben, wie viel Speicher die Liste insgesamt benötigt.
Dazu bitte die Hilfe konsultieren: TList.Capacity

Ahhh, das ist einfach und praktisch, eingebaut.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconglotzer hat folgendes geschrieben Zum zitierten Posting springen:

Das geht so leider nicht, der Grund ist ein bischen komplizierter: Es gibt ein paar Materialien die beim Programm dabei sind, die werden über "lib.[untergruppe].[materialname]" angesprochen.
Zusätzlich kann man aber auch eigene Materialien mit der Karte mitliefern, oder Materialien anderer Karten verwenden. Das geht dann mit "[mapname].[untergruppe].[materialname]"

Wenn man zuerst eine Liste mit den Namen der Materialien speichert:
"lib.[untergruppe].[materialname]"
"lib.[untergruppe].[materialname]"
"[mapname].[untergruppe].[materialname]"
...
genügt es für jede einzelne Kachel den Index in dieser Liste zu speichern.

Gute Idee, eingebaut.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconglotzer hat folgendes geschrieben Zum zitierten Posting springen:
Das procedure TMySaveVisitorFileformatXXX.SaveFieldType(AVisited: TMapData); hingegen sagt mir garnix. Könntest du das etwas näher erklären?

Bei den meisten Integerwerten ebenso wie bei Aufzählungstypen(sind auch nur Integer) ist meist nur das unteren Byte belegt.
Speichere ich in der Datei Kachel für Kachel so liegt dort zuerst eine MaterialID, dann ein FieldTyp usw. Bei der nächsten Kachel geht das ganze von vorn los. Solche Daten lassen sich relativ schlecht komprimieren.

1. Schritt
Man speichert alle gleichartigen Daten in einem Block, zuerst die MaterialID aller Felder, dann den Feldtyp aller Felder usw.
Beispiel:
FT_Blocked, FT_Blocked, FT_Blocked, FT_BuildableGround, FT_BuildableGround, FT_Tower
01 00 00 00, 01 00 00 00, 01 00 00 00, 00 00 00 00, 00 00 00 00, 02 00 00 00

2. Schritt
Innerhalb eines Blocks sortiert man die Byte um, damit gleichwertige Byte hintereinander liegen, hier am Beispiel FieldTyp
Zuerst das untere Byte aller FieldTyp Felder, dann das nächst höhere, usw.
Beispiel:
01 01 01 00 00 02, 00 00 00 00 00, 00 00 00 00 00, 00 00 00 00 00

Die Daten lassen sich so viel besser komprimieren und entsprechend schneller laden.

Ehrlich gesagt find ich das zu kompliziert. Und bei einer Dateigröße von 500kb bis 5mb irgendwie auch sehr viel aufwand für wenig Gewinnn.

Hab jetzt mal Testweiße TFastFileStream durch TMemoryStream ersetzt, Ergebnis: alles in weniger als 200ms für ein 500x500 Feld gespeichert :D

Vielen Dank an alle die geholfen haben, ich bin mit 200ms zufrieden.

_________________
ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht