Autor |
Beitrag |
glotzer
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
Grüße
glotzer
Die Datenverwaltungsklasse:
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;
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')); 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; 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 if ReadStringFromStream(stream) <> MapFileMagicWord then begin raise Exception.Create('File "'+FileName+'" is not a valide map file.'); end; ReadUInt64FromStream(stream,FileVersion);
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 WriteStringToStream(stream,MapFileMagicWord); WriteUInt64ToStream(stream,MapFileVersion_Cur); SizePos := Stream.Position; WriteUInt64ToStream(stream,0);
WriteUInt64ToStream(stream,width); WriteUInt64ToStream(stream,height);
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 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; 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:
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
Beiträge: 4764
Erhaltene Danke: 1052
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: Mo 03.09.12 10:03
_________________ ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
|
|
Th69
Beiträge: 4764
Erhaltene Danke: 1052
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
_________________ ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
|
|
glotzer
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
Beiträge: 4764
Erhaltene Danke: 1052
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
Beiträge: 4764
Erhaltene Danke: 1052
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: 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
Beiträge: 173
Erhaltene Danke: 43
|
Verfasst: 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.
Delphi-Quelltext
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:
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 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
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: 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...
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.
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
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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: Di 04.09.12 00:50
Erstmal vielen Dank dass ihr euch die Mühe macht euch das Ganze anzuschauen
Blup hat folgendes geschrieben : | 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.
Blup hat folgendes geschrieben : |
Im "Resize" unbedingt vorab "FFields.Capacity" setzen.
|
was meinst du damit?
Blup hat folgendes geschrieben : | ]
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.
Blup hat folgendes geschrieben : | ]
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:
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 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?
Martok hat folgendes geschrieben : |
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
|
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:
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
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; 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 if ReadStringFromStream(stream) <> MapFileMagicWord then begin raise Exception.Create('File "'+FileName+'" is not a valide map file.'); end; ReadLongWordFromStream(stream,FileVersion);
if ReadLongWordFromStream(stream) <> stream.Size then begin raise Exception.Create('File size is wrong, the map file "'+FileName+'" is corrupted.'); end;
ReadLongWordFromStream(stream,w); ReadLongWordFromStream(stream,h); Resize(w,h); end;
procedure ReadMaterialList; var OldPos: integer; FieldListSize,MaterialListCount: LongWord; I: Integer; begin ReadLongWordFromStream(stream,FieldListSize); OldPos := stream.position; stream.position := stream.position + FieldListSize;
ReadLongWordFromStream(stream,MaterialListCount); for I := 0 to MaterialListCount do begin Materials.Add(ReadStringFromStream(stream)); end; 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 WriteStringToStream(stream,MapFileMagicWord); WriteLongWordToStream(stream,MapFileVersion_Cur); FileSizePos := Stream.Position; WriteLongWordToStream(stream,0); WriteLongWordToStream(stream,width); WriteLongWordToStream(stream,height); 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 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;
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
Beiträge: 173
Erhaltene Danke: 43
|
Verfasst: Di 04.09.12 10:12
glotzer hat folgendes geschrieben : |
Blup hat folgendes geschrieben : |
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
glotzer hat folgendes geschrieben : |
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.
glotzer hat folgendes geschrieben : | 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
Beiträge: 393
Erhaltene Danke: 49
Win 7
Lazarus
|
Verfasst: Di 04.09.12 13:49
Blup hat folgendes geschrieben : |
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.
Blup hat folgendes geschrieben : | glotzer hat folgendes geschrieben : |
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.
Blup hat folgendes geschrieben : | glotzer hat folgendes geschrieben : | 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
Vielen Dank an alle die geholfen haben, ich bin mit 200ms zufrieden.
_________________ ja, ich schreibe grundsätzlich alles klein und meine rechtschreibfehler sind absicht
|
|
|