From 3f758d569aeb250474aece9c4051f05ad7362805 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Sun, 3 Sep 2017 00:03:18 +0300 Subject: [PATCH] correctly freeing dynrecords; as a consequence, no more memory leaks in custom game selector --- src/game/g_gfx.pas | 1 + src/game/g_gui.pas | 75 ++++++++++++---------- src/game/g_map.pas | 39 +++++++---- src/game/g_triggers.pas | 2 +- src/shared/xdynrec.pas | 139 +++++++++++++--------------------------- 5 files changed, 114 insertions(+), 142 deletions(-) diff --git a/src/game/g_gfx.pas b/src/game/g_gfx.pas index 47c638c..e49a72c 100644 --- a/src/game/g_gfx.pas +++ b/src/game/g_gfx.pas @@ -868,6 +868,7 @@ begin begin pan := g_Map_PanelAtPoint(x, y, GridTagObstacle); end; + if (pan <> nil) then continue; env := TEnvType.EAir; // color diff --git a/src/game/g_gui.pas b/src/game/g_gui.pas index d7735ab..4f4a2c3 100644 --- a/src/game/g_gui.pas +++ b/src/game/g_gui.pas @@ -2810,61 +2810,66 @@ begin try map := g_Map_ParseMap(Data, Len); except + FreeMem(Data); map.Free(); - raise; + //raise; + exit; end; FreeMem(Data); - panlist := map.field['panel']; - //header := GetMapHeader(map); + try + panlist := map.field['panel']; + //header := GetMapHeader(map); - FMapSize.X := map.Width div 16; - FMapSize.Y := map.Height div 16; + FMapSize.X := map.Width div 16; + FMapSize.Y := map.Height div 16; - rX := Ceil(map.Width / (MAPPREVIEW_WIDTH*256.0)); - rY := Ceil(map.Height / (MAPPREVIEW_HEIGHT*256.0)); - FScale := max(rX, rY); + rX := Ceil(map.Width / (MAPPREVIEW_WIDTH*256.0)); + rY := Ceil(map.Height / (MAPPREVIEW_HEIGHT*256.0)); + FScale := max(rX, rY); - FMapData := nil; + FMapData := nil; - if (panlist <> nil) then - begin - for pan in panlist do + if (panlist <> nil) then begin - if (pan.PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or - PANEL_STEP or PANEL_WATER or - PANEL_ACID1 or PANEL_ACID2)) <> 0 then + for pan in panlist do begin - SetLength(FMapData, Length(FMapData)+1); - with FMapData[High(FMapData)] do + if (pan.PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or + PANEL_STEP or PANEL_WATER or + PANEL_ACID1 or PANEL_ACID2)) <> 0 then begin - X1 := pan.X div 16; - Y1 := pan.Y div 16; + SetLength(FMapData, Length(FMapData)+1); + with FMapData[High(FMapData)] do + begin + X1 := pan.X div 16; + Y1 := pan.Y div 16; - X2 := (pan.X + pan.Width) div 16; - Y2 := (pan.Y + pan.Height) div 16; + X2 := (pan.X + pan.Width) div 16; + Y2 := (pan.Y + pan.Height) div 16; - X1 := Trunc(X1/FScale + 0.5); - Y1 := Trunc(Y1/FScale + 0.5); - X2 := Trunc(X2/FScale + 0.5); - Y2 := Trunc(Y2/FScale + 0.5); + X1 := Trunc(X1/FScale + 0.5); + Y1 := Trunc(Y1/FScale + 0.5); + X2 := Trunc(X2/FScale + 0.5); + Y2 := Trunc(Y2/FScale + 0.5); - if (X1 <> X2) or (Y1 <> Y2) then - begin - if X1 = X2 then - X2 := X2 + 1; - if Y1 = Y2 then - Y2 := Y2 + 1; - end; + if (X1 <> X2) or (Y1 <> Y2) then + begin + if X1 = X2 then + X2 := X2 + 1; + if Y1 = Y2 then + Y2 := Y2 + 1; + end; - PanelType := pan.PanelType; + PanelType := pan.PanelType; + end; end; end; end; + finally + //writeln('freeing map'); + map.Free(); end; - - map.Free(); end; procedure TGUIMapPreview.ClearMap(); diff --git a/src/game/g_map.pas b/src/game/g_map.pas index 395a2cc..ae8ee10 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -273,6 +273,7 @@ function g_Map_MaxY (): Integer; inline; begin if (mapGrid <> nil) then result : var dfmapdef: TDynMapDef = nil; + procedure loadMapDefinition (); var pr: TTextParser = nil; @@ -280,13 +281,15 @@ var WAD: TWADFile = nil; begin if (dfmapdef <> nil) then exit; + try e_LogWritefln('parsing "mapdef.txt"...', []); st := openDiskFileRO(DataDir+'mapdef.txt'); + e_LogWritefln('found local "%smapdef.txt"', [DataDir]); except st := nil; - e_LogWritefln('local "%smapdef.txt" not found', [DataDir]); end; + if (st = nil) then begin WAD := TWADFile.Create(); @@ -301,15 +304,22 @@ begin end; end; - if (st = nil) then - begin - //raise Exception.Create('cannot open "mapdef.txt"'); - e_LogWritefln('using default "mapdef.txt"...', [], MSG_WARNING); - pr := TStrTextParser.Create(defaultMapDef); - end - else - begin - pr := TFileTextParser.Create(st); + try + if (st = nil) then + begin + //raise Exception.Create('cannot open "mapdef.txt"'); + e_LogWriteln('using default "mapdef.txt"...'); + pr := TStrTextParser.Create(defaultMapDef); + end + else + begin + pr := TFileTextParser.Create(st); + end; + except on e: Exception do + begin + e_LogWritefln('something is VERY wrong here! -- ', [e.message]); + raise; + end; end; try @@ -331,6 +341,8 @@ var begin result := nil; if (dataLen < 4) then exit; + + if (dfmapdef = nil) then writeln('need to load mapdef'); loadMapDefinition(); if (dfmapdef = nil) then raise Exception.Create('internal map loader error'); @@ -340,6 +352,7 @@ begin begin // binary map try + //e_LogWriteln('parsing binary map...'); result := dfmapdef.parseBinMap(wst); except on e: Exception do begin @@ -356,6 +369,7 @@ begin // text map pr := TFileTextParser.Create(wst); try + //e_LogWriteln('parsing text map...'); result := dfmapdef.parseMap(pr); except on e: Exception do begin @@ -368,6 +382,7 @@ begin end; pr.Free(); // will free `wst` end; + //e_LogWriteln('map parsed.'); end; @@ -1313,7 +1328,7 @@ begin end else begin - trigData := Trigger.trigRec.clone(); + trigData := Trigger.trigRec.clone(nil); end; end; @@ -2203,6 +2218,8 @@ begin mapReader := g_Map_ParseMap(Data, Len); except mapReader := nil; + FreeMem(Data); + exit; end; FreeMem(Data); diff --git a/src/game/g_triggers.pas b/src/game/g_triggers.pas index 53b023b..1c8a586 100644 --- a/src/game/g_triggers.pas +++ b/src/game/g_triggers.pas @@ -2128,7 +2128,7 @@ begin Trigger.mapIndex := mapidx; if (Trigger.trigData.trigRec <> nil) then begin - Trigger.trigData := Trigger.trigData.trigRec.clone(); + Trigger.trigData := Trigger.trigData.trigRec.clone({Trigger.trigData.headerRec}nil); end else begin diff --git a/src/shared/xdynrec.pas b/src/shared/xdynrec.pas index c71250c..e1bd571 100644 --- a/src/shared/xdynrec.pas +++ b/src/shared/xdynrec.pas @@ -123,7 +123,7 @@ type function definition (): AnsiString; function pasdef (): AnsiString; - function clone (newOwner: TDynRecord=nil): TDynField; + function clone (newOwner: TDynRecord=nil; registerIn: TDynRecord=nil): TDynField; procedure parseValue (pr: TTextParser); procedure parseBinValue (st: TStream); @@ -191,6 +191,8 @@ type mTagInt: Integer; mTagPtr: Pointer; + mRec2Free: TDynRecList; + private procedure parseDef (pr: TTextParser); // parse definition @@ -206,6 +208,8 @@ type function getForTrigCount (): Integer; inline; function getForTrigAt (idx: Integer): AnsiString; inline; + procedure regrec (rec: TDynRecord); + protected function findRecordByTypeId (const atypename, aid: AnsiString): TDynRecord; function findRecordNumByType (const atypename: AnsiString; rc: TDynRecord): Integer; @@ -222,7 +226,7 @@ type function definition (): AnsiString; function pasdef (): AnsiString; - function clone (): TDynRecord; + function clone (registerIn: TDynRecord): TDynRecord; function isSimpleEqu (rec: TDynRecord): Boolean; @@ -501,7 +505,7 @@ begin end; -function TDynField.clone (newOwner: TDynRecord=nil): TDynField; +function TDynField.clone (newOwner: TDynRecord=nil; registerIn: TDynRecord=nil): TDynField; var rec: TDynRecord; begin @@ -518,7 +522,7 @@ begin begin if (result.mRVal = nil) then result.mRVal := TDynRecList.Create(mRVal.count); if (result.mRHash = nil) then result.mRHash := hashNewStrInt(); - for rec in mRVal do result.addListItem(rec.clone()); + for rec in mRVal do result.addListItem(rec.clone(registerIn)); end; result.mRecRef := mRecRef; result.mMaxDim := mMaxDim; @@ -1414,7 +1418,7 @@ begin if (tfld = nil) then raise Exception.Create(Format('triggerdata value for field ''%s'' in record ''%s'' without TriggerType field', [mName, rec.mName])); rc := mOwner.mOwner.findTrigFor(tfld.mSVal); // find in mapdef if (rc = nil) then raise Exception.Create(Format('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName, rec.mName, tfld.mSVal])); - rc := rc.clone(); + rc := rc.clone(mOwner.mHeaderRec); rc.mHeaderRec := mOwner.mHeaderRec; try rc.parseBinValue(st, true); @@ -1616,7 +1620,7 @@ begin if (tfld = nil) then raise Exception.Create(Format('triggerdata value for field ''%s'' in record ''%s'' without ''type'' field', [mName, rec.mName])); rc := mOwner.mOwner.findTrigFor(tfld.mSVal); // find in mapdef if (rc = nil) then raise Exception.Create(Format('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName, rec.mName, tfld.mSVal])); - rc := rc.clone(); + rc := rc.clone(mOwner.mHeaderRec); rc.mHeaderRec := mOwner.mHeaderRec; //writeln(rc.definition); try @@ -1655,7 +1659,7 @@ begin rec := nil; if (mEBSType <> nil) and (mEBSType is TDynRecord) then rec := (mEBSType as TDynRecord); if (rec = nil) then raise Exception.Create(Format('record type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName])); - rc := rec.clone(); + rc := rec.clone(mOwner.mHeaderRec); rc.mHeaderRec := mOwner.mHeaderRec; rc.parseValue(pr); mRecRef := rc; @@ -1852,12 +1856,23 @@ begin mHeaderRec := nil; mTagInt := 0; mTagPtr := nil; + mRec2Free := nil; end; destructor TDynRecord.Destroy (); +var + fld: TDynField; + rec: TDynRecord; begin + if (mRec2Free <> nil) then + begin + for rec in mRec2Free do if (rec <> self) then rec.Free(); + mRec2Free.Free(); + mRec2Free := nil; + end; mName := ''; + for fld in mFields do fld.Free(); mFields.Free(); mFields := nil; {$IF DEFINED(XDYNREC_USE_FIELDHASH)} @@ -1872,6 +1887,16 @@ begin end; +procedure TDynRecord.regrec (rec: TDynRecord); +begin + if (rec <> nil) and (rec <> self) then + begin + if (mRec2Free = nil) then mRec2Free := TDynRecList.Create(); + mRec2Free.append(rec); + end; +end; + + procedure TDynRecord.addField (fld: TDynField); inline; begin if (fld = nil) then raise Exception.Create('cannot append nil field to record'); @@ -1967,7 +1992,7 @@ begin end; -function TDynRecord.clone (): TDynRecord; +function TDynRecord.clone (registerIn: TDynRecord): TDynRecord; var fld: TDynField; f: Integer; @@ -1978,18 +2003,19 @@ begin result.mPasName := mPasName; result.mName := mName; result.mSize := mSize; + result.mHeader := mHeader; + result.mBinBlock := mBinBlock; + result.mHeaderRec := mHeaderRec; + result.mTagInt := mTagInt; + result.mTagPtr := mTagPtr; if (mFields.count > 0) then begin result.mFields.capacity := mFields.count; - for fld in mFields do result.addField(fld.clone(result)); + for fld in mFields do result.addField(fld.clone(result, registerIn)); end; SetLength(result.mTrigTypes, Length(mTrigTypes)); for f := 0 to High(mTrigTypes) do result.mTrigTypes[f] := mTrigTypes[f]; - result.mHeader := mHeader; - result.mBinBlock := mBinBlock; - result.mHeaderRec := mHeaderRec; - result.mTagInt := mTagInt; - result.mTagPtr := mTagPtr; + if (registerIn <> nil) then registerIn.regrec(result); end; @@ -2132,83 +2158,6 @@ begin end; -{ -procedure TDynRecord.setUserField (const fldname: AnsiString; v: LongInt); -var - fld: TDynField; -begin - if (Length(fldname) = 0) then exit; - fld := field[fldname]; - if (fld <> nil) then - begin - if (fld.mType <> fld.TType.TInt) or (fld.mEBS <> fld.TEBS.TNone) then - begin - raise Exception.Create(Format('invalid user field ''%s'' type', [fld.name])); - end; - end - else - begin - fld := TDynField.Create(fldname, fld.TType.TInt); - fld.mOwner := self; - fld.mIVal := v; - fld.mInternal := true; - fld.mDefined := true; - addField(fld); - end; -end; - - -procedure TDynRecord.setUserField (const fldname: AnsiString; v: AnsiString); -var - fld: TDynField; -begin - if (Length(fldname) = 0) then exit; - fld := field[fldname]; - if (fld <> nil) then - begin - if (fld.mType <> fld.TType.TString) or (fld.mEBS <> fld.TEBS.TNone) then - begin - raise Exception.Create(Format('invalid user field ''%s'' type', [fld.name])); - end; - end - else - begin - fld := TDynField.Create(fldname, fld.TType.TString); - fld.mOwner := self; - fld.mSVal := v; - fld.mInternal := true; - fld.mDefined := true; - addField(fld); - end; -end; - - -procedure TDynRecord.setUserField (const fldname: AnsiString; v: Boolean); -var - fld: TDynField; -begin - if (Length(fldname) = 0) then exit; - fld := field[fldname]; - if (fld <> nil) then - begin - if (fld.mType <> fld.TType.TBool) or (fld.mEBS <> fld.TEBS.TNone) then - begin - raise Exception.Create(Format('invalid user field ''%s'' type', [fld.name])); - end; - end - else - begin - fld := TDynField.Create(fldname, fld.TType.TBool); - fld.mOwner := self; - fld.mIVal := Integer(v); - fld.mInternal := true; - fld.mDefined := true; - addField(fld); - end; -end; -} - - procedure TDynRecord.parseDef (pr: TTextParser); var fld: TDynField; @@ -2441,7 +2390,7 @@ begin for f := 0 to (bsize div rec.mSize)-1 do begin mst.setup(buf+f*rec.mSize, rec.mSize); - rec := rect.clone(); + rec := rect.clone(self); rec.mHeaderRec := self; rec.parseBinValue(mst); rec.mId := Format('%s%d', [rec.mName, f]); @@ -2709,7 +2658,7 @@ begin if (trc <> nil) then begin {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF} - rec := trc.clone(); + rec := trc.clone(mHeaderRec); {$IF DEFINED(D2D_DYNREC_PROFILER)}profCloneRec := curTimeMicro()-stt;{$ENDIF} rec.mHeaderRec := mHeaderRec; try @@ -3147,7 +3096,7 @@ begin result := nil; try pr.expectId(headerType.name); - res := headerType.clone(); + res := headerType.clone(nil); res.mHeaderRec := res; res.parseValue(pr); result := res; @@ -3164,7 +3113,7 @@ var begin result := nil; try - res := headerType.clone(); + res := headerType.clone(nil); res.mHeaderRec := res; res.parseBinValue(st); result := res; -- 2.29.2