X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2Fxdynrec.pas;h=12d7256429d473c5f46061d7a7fe4084d2251f97;hb=96165a1a95f0ea18a20dd8578fade4b46ec23746;hp=105a755439d4c846345933270ad6da24f5b455f0;hpb=3049aec7dbcc5e50e0f59be54ee94cf4e10e4c6a;p=d2df-sdl.git diff --git a/src/shared/xdynrec.pas b/src/shared/xdynrec.pas index 105a755..12d7256 100644 --- a/src/shared/xdynrec.pas +++ b/src/shared/xdynrec.pas @@ -14,6 +14,7 @@ * along with this program. If not, see . *) {$INCLUDE a_modes.inc} +{.$DEFINE XDYNREC_USE_FIELDHASH} // actually, it is SLOWER with this unit xdynrec; interface @@ -62,7 +63,6 @@ type mRecRef: TDynRecord; // for TEBS.TRec mMaxDim: Integer; // for byte and char arrays; <0: not an array; 0: impossible value mBinOfs: Integer; // offset in binary; <0 - none - mRecOfs: Integer; // offset in record; <0 - none mSepPosSize: Boolean; // for points and sizes, use separate fields mAsT: Boolean; // for points and sizes, use separate fields, names starts with `t` mDefined: Boolean; @@ -84,6 +84,10 @@ type // for binary parser mRecRefId: AnsiString; + // for userdata + mTagInt: Integer; + mTagPtr: Pointer; + private procedure cleanup (); @@ -94,7 +98,31 @@ type function isDefaultValue (): Boolean; function getListCount (): Integer; inline; - function getListItem (idx: Integer): TDynRecord; inline; + function getListItem (idx: Integer): TDynRecord; inline; overload; + function getListItem (const aname: AnsiString): TDynRecord; inline; overload; + + function getRecRefIndex (): Integer; + + procedure setIVal (v: Integer); inline; + + protected + // returns `true` for duplicate record id + function addListItem (rec: TDynRecord): Boolean; inline; + + public + { + type + TListEnumerator = record + private + mList: TDynRecList; + mCurIdx: Integer; + public + constructor Create (alist: TDynRecList); + function MoveNext (): Boolean; inline; + function getCurrent (): TDynRecord; inline; + property Current: TDynRecord read getCurrent; + end; + } public constructor Create (const aname: AnsiString; atype: TType); @@ -119,27 +147,35 @@ type procedure setValue (const s: AnsiString); + function GetEnumerator (): TDynRecList.TEnumerator; inline; + public property pasname: AnsiString read mPasName; property name: AnsiString read mName; property baseType: TType read mType; + property negbool: Boolean read mNegBool; property defined: Boolean read mDefined write mDefined; property internal: Boolean read mInternal write mInternal; - property ival: Integer read mIVal; + property hasTPrefix: Boolean read mAsT; + property separatePasFields: Boolean read mSepPosSize; + property binOfs: Integer read mBinOfs; + property ival: Integer read mIVal write setIVal; + property ival2: Integer read mIVal2; property sval: AnsiString read mSVal; property hasDefault: Boolean read mHasDefault; property defsval: AnsiString read mDefSVal; property ebs: TEBS read mEBS; property ebstype: TObject read mEBSType; property ebstypename: AnsiString read mEBSTypeName; // enum/bitset name - //property list: TDynRecList read mRVal; // for list + property recref: TDynRecord read mRecRef write mRecRef; //FIXME: writing is a hack! + property recrefIndex: Integer read getRecRefIndex; // search for this record in header; -1: not found + // for lists property count: Integer read getListCount; property item[idx: Integer]: TDynRecord read getListItem; - - property x: Integer read mIVal; - property w: Integer read mIVal; - property y: Integer read mIVal2; - property h: Integer read mIVal2; + property items[const aname: AnsiString]: TDynRecord read getListItem; default; // alas, FPC 3+ lost property overloading feature + // userdata + property tagInt: Integer read mTagInt write mTagInt; + property tagPtr: Pointer read mTagPtr write mTagPtr; end; @@ -152,25 +188,40 @@ type mName: AnsiString; mSize: Integer; mFields: TDynFieldList; + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + mFieldsHash: THashStrInt; // id -> index in mRVal + {$ENDIF} mTrigTypes: array of AnsiString; // if this is triggerdata, we'll hold list of triggers here mHeader: Boolean; // true for header record mBinBlock: Integer; // -1: none mHeaderRec: TDynRecord; // for "value" records this is header record with data, for "type" records this is header type record + // for userdata + mTagInt: Integer; + mTagPtr: Pointer; + private procedure parseDef (pr: TTextParser); // parse definition function findByName (const aname: AnsiString): Integer; inline; function hasByName (const aname: AnsiString): Boolean; inline; function getFieldByName (const aname: AnsiString): TDynField; inline; + function getFieldAt (idx: Integer): TDynField; inline; + function getCount (): Integer; inline; function getIsTrigData (): Boolean; inline; function getIsForTrig (const aname: AnsiString): Boolean; inline; + function getForTrigCount (): Integer; inline; + function getForTrigAt (idx: Integer): AnsiString; inline; + protected function findRecordByTypeId (const atypename, aid: AnsiString): TDynRecord; function findRecordNumByType (const atypename: AnsiString; rc: TDynRecord): Integer; - procedure addRecordByType (const atypename: AnsiString; rc: TDynRecord); + function addRecordByType (const atypename: AnsiString; rc: TDynRecord): Boolean; // `true`: duplicate record id + + procedure addField (fld: TDynField); inline; + function addFieldChecked (fld: TDynField): Boolean; inline; // `true`: duplicate name public constructor Create (); @@ -196,18 +247,29 @@ type // number of records of the given instance function instanceCount (const typename: AnsiString): Integer; + procedure setUserField (const fldname: AnsiString; v: LongInt); + procedure setUserField (const fldname: AnsiString; v: AnsiString); + procedure setUserField (const fldname: AnsiString; v: Boolean); + public property id: AnsiString read mId; // for map parser property pasname: AnsiString read mPasName; property name: AnsiString read mName; // record name property size: Integer read mSize; // size in bytes - property fields: TDynFieldList read mFields; + //property fields: TDynFieldList read mFields; property has[const aname: AnsiString]: Boolean read hasByName; - property field[const aname: AnsiString]: TDynField read getFieldByName; + property count: Integer read getCount; + property field[const aname: AnsiString]: TDynField read getFieldByName; default; + property fieldAt[idx: Integer]: TDynField read getFieldAt; property isTrigData: Boolean read getIsTrigData; property isForTrig[const aname: AnsiString]: Boolean read getIsForTrig; - property headerType: TDynRecord read mHeaderRec; + property forTrigCount: Integer read getForTrigCount; + property forTrigAt[idx: Integer]: AnsiString read getForTrigAt; + property headerRec: TDynRecord read mHeaderRec; property isHeader: Boolean read mHeader; + // userdata + property tagInt: Integer read mTagInt write mTagInt; + property tagPtr: Pointer read mTagPtr write mTagPtr; end; TDynEBS = class @@ -258,6 +320,9 @@ type function getHeaderRecType (): TDynRecord; inline; + function getTrigTypeCount (): Integer; inline; + function getTrigTypeAt (idx: Integer): TDynRecord; inline; + public constructor Create (pr: TTextParser); // parses data definition destructor Destroy (); override; @@ -267,6 +332,7 @@ type function findEBSType (const aname: AnsiString): TDynEBS; function pasdef (): AnsiString; + function pasdefconst (): AnsiString; // creates new header record function parseMap (pr: TTextParser): TDynRecord; @@ -276,6 +342,8 @@ type public property headerType: TDynRecord read getHeaderRecType; + property trigTypeCount: Integer read getTrigTypeCount; + property trigType[idx: Integer]: TDynRecord read getTrigTypeAt; end; @@ -295,6 +363,36 @@ uses function StrEqu (const a, b: AnsiString): Boolean; inline; begin result := (a = b); end; +// ////////////////////////////////////////////////////////////////////////// // +{ +constructor TDynField.TListEnumerator.Create (alist: TDynRecList); +begin + mList := alist; + mCurIdx := -1; +end; + + +function TDynField.TListEnumerator.MoveNext (): Boolean; inline; +begin + Inc(mCurIdx); + result := (mList <> nil) and (mCurIdx < mList.count); +end; + + +function TDynField.TListEnumerator.getCurrent (): TDynRecord; inline; +begin + result := mList[mCurIdx]; +end; +} + + +function TDynField.GetEnumerator (): TDynRecList.TEnumerator; inline; +begin + //result := TListEnumerator.Create(mRVal); + if (mRVal <> nil) then result := mRVal.GetEnumerator else result := TDynRecList.TEnumerator.Create(nil, 0); +end; + + // ////////////////////////////////////////////////////////////////////////// // constructor TDynField.Create (const aname: AnsiString; atype: TType); begin @@ -340,7 +438,6 @@ begin mRecRef := nil; mMaxDim := -1; mBinOfs := -1; - mRecOfs := -1; mSepPosSize := false; mAsT := false; mHasDefault := false; @@ -359,12 +456,14 @@ begin mAsMonsterId := false; mNegBool := false; mRecRefId := ''; + mTagInt := 0; + mTagPtr := nil; end; function TDynField.clone (newOwner: TDynRecord=nil): TDynField; var - rec, nrc: TDynRecord; + rec: TDynRecord; begin result := TDynField.Create(mName, mType); result.mOwner := mOwner; @@ -379,17 +478,11 @@ 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 - begin - nrc := rec.clone(); - result.mRVal.append(nrc); - if (Length(nrc.mId) > 0) then result.mRHash.put(nrc.mId, result.mRVal.count-1); - end; + for rec in mRVal do result.addListItem(rec.clone()); end; result.mRecRef := mRecRef; result.mMaxDim := mMaxDim; result.mBinOfs := mBinOfs; - result.mRecOfs := mRecOfs; result.mSepPosSize := mSepPosSize; result.mAsT := mAsT; result.mDefined := mDefined; @@ -408,6 +501,16 @@ begin result.mEBSTypeName := mEBSTypeName; result.mEBSType := mEBSType; result.mRecRefId := mRecRefId; + result.mTagInt := mTagInt; + result.mTagPtr := mTagPtr; +end; + + +procedure TDynField.setIVal (v: Integer); inline; +begin + //FIXME: check type + mIVal := v; + mDefined := true; end; @@ -531,12 +634,31 @@ begin end; -function TDynField.getListItem (idx: Integer): TDynRecord; inline; +function TDynField.getListItem (idx: Integer): TDynRecord; inline; overload; begin if (mRVal <> nil) and (idx >= 0) and (idx < mRVal.count) then result := mRVal[idx] else result := nil; end; +function TDynField.getListItem (const aname: AnsiString): TDynRecord; inline; overload; +var + idx: Integer; +begin + if (mRVal <> nil) and mRHash.get(aname, idx) then result := mRVal[idx] else result := nil; +end; + + +function TDynField.addListItem (rec: TDynRecord): Boolean; inline; +begin + result := false; + if (mRVal <> nil) then + begin + mRVal.append(rec); + if (Length(rec.mId) > 0) then result := mRHash.put(rec.mId, mRVal.count-1); + end; +end; + + class function TDynField.getTypeName (t: TType): AnsiString; begin case t of @@ -563,7 +685,7 @@ begin result := mPasName+' is '+quoteStr(mName)+' type '; result += getTypeName(mType); if (mMaxDim >= 0) then result += Format('[%d]', [mMaxDim]); - if (mRecOfs >= 0) then result += Format(' offset %d', [mRecOfs]); + if (mBinOfs >= 0) then result += Format(' offset %d', [mBinOfs]); case mEBS of TEBS.TNone: begin end; TEBS.TRec: result += ' '+mEBSTypeName; @@ -787,7 +909,6 @@ begin self.mAsMonsterId := asmonid; self.mMaxDim := lmaxdim; self.mBinOfs := fldofs; - self.mRecOfs := fldofs; self.mSepPosSize := (asxy or aswh); self.mAsT := ast; self.mOmitDef := omitdef; @@ -795,6 +916,13 @@ begin end; +function TDynField.getRecRefIndex (): Integer; +begin + if (mRecRef = nil) then begin result := -1; exit; end; + result := mOwner.findRecordNumByType(mEBSTypeName, mRecRef); +end; + + procedure TDynField.writeBinTo (st: TStream); var s: AnsiString; @@ -1095,6 +1223,7 @@ begin raise Exception.Create(Format('cannot parse field ''%s'' yet', [mName])); end; + procedure TDynField.parseBinValue (st: TStream); var rec, rc: TDynRecord; @@ -1364,7 +1493,11 @@ begin rc.parseValue(pr); mRecRef := rc; mDefined := true; - mOwner.addRecordByType(mEBSTypeName, rc); + if mOwner.addRecordByType(mEBSTypeName, rc) then + begin + //raise Exception.Create(Format('record type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName])); + e_LogWritefln('duplicate record with id ''%s'' for field ''%s'' in record ''%s''', [rc.mId, mName, mOwner.mName]); + end; pr.eatTT(pr.TTSemi); // hack: allow (but don't require) semicolon after inline records exit; end; @@ -1526,10 +1659,15 @@ begin mName := ''; mSize := 0; mFields := TDynFieldList.Create(); + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + mFieldsHash := hashNewStrInt(); + {$ENDIF} mTrigTypes := nil; mHeader := false; mHeaderRec := nil; mBinBlock := -1; + mTagInt := 0; + mTagPtr := nil; parseDef(pr); end; @@ -1539,9 +1677,14 @@ begin mName := ''; mSize := 0; mFields := TDynFieldList.Create(); + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + mFieldsHash := hashNewStrInt(); + {$ENDIF} mTrigTypes := nil; mHeader := false; mHeaderRec := nil; + mTagInt := 0; + mTagPtr := nil; end; @@ -1550,14 +1693,47 @@ begin mName := ''; mFields.Free(); mFields := nil; + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + mFieldsHash.Free(); + mFieldsHash := nil; + {$ENDIF} mTrigTypes := nil; mHeaderRec := nil; + mTagInt := 0; + mTagPtr := nil; inherited; end; +procedure TDynRecord.addField (fld: TDynField); inline; +begin + if (fld = nil) then raise Exception.Create('cannot append nil field to record'); + mFields.append(fld); + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + if (Length(fld.mName) > 0) then mFieldsHash.put(fld.mName, mFields.count-1); + {$ENDIF} +end; + + +function TDynRecord.addFieldChecked (fld: TDynField): Boolean; inline; // `true`: duplicate name +begin + result := false; + if (fld = nil) then raise Exception.Create('cannot append nil field to record'); + {$IF not DEFINED(XDYNREC_USE_FIELDHASH)} + if (Length(fld.mName) > 0) then result := hasByName(fld.mName); + {$ENDIF} + mFields.append(fld); + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + if (Length(fld.mName) > 0) then result := mFieldsHash.put(fld.mName, mFields.count-1); + {$ENDIF} +end; + + function TDynRecord.findByName (const aname: AnsiString): Integer; inline; begin + {$IF DEFINED(XDYNREC_USE_FIELDHASH)} + if not mFieldsHash.get(aname, result) then result := -1; + {$ELSE} result := 0; while (result < mFields.count) do begin @@ -1565,6 +1741,7 @@ begin Inc(result); end; result := -1; + {$ENDIF} end; @@ -1583,6 +1760,18 @@ begin end; +function TDynRecord.getFieldAt (idx: Integer): TDynField; inline; +begin + if (idx >= 0) and (idx < mFields.count) then result := mFields[idx] else result := nil; +end; + + +function TDynRecord.getCount (): Integer; inline; +begin + result := mFields.count; +end; + + function TDynRecord.getIsTrigData (): Boolean; inline; begin result := (Length(mTrigTypes) > 0); @@ -1599,6 +1788,18 @@ begin end; +function TDynRecord.getForTrigCount (): Integer; inline; +begin + result := Length(mTrigTypes); +end; + + +function TDynRecord.getForTrigAt (idx: Integer): AnsiString; inline; +begin + if (idx >= 0) and (idx < Length(mTrigTypes)) then result := mTrigTypes[idx] else result := ''; +end; + + function TDynRecord.clone (): TDynRecord; var fld: TDynField; @@ -1613,20 +1814,21 @@ begin if (mFields.count > 0) then begin result.mFields.capacity := mFields.count; - for fld in mFields do result.mFields.append(fld.clone(result)); + for fld in mFields do result.addField(fld.clone(result)); 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; end; function TDynRecord.findRecordByTypeId (const atypename, aid: AnsiString): TDynRecord; var fld: TDynField; - //rec: TDynRecord; idx: Integer; begin result := nil; @@ -1638,12 +1840,6 @@ begin // find by id if (fld.mRVal <> nil) then begin - { - for rec in fld.mRVal do - begin - if StrEqu(rec.mId, aid) then begin result := rec; exit; end; - end; - } if fld.mRHash.get(aid, idx) then begin result := fld.mRVal[idx]; exit; end; end; // alas @@ -1672,7 +1868,7 @@ begin end; -procedure TDynRecord.addRecordByType (const atypename: AnsiString; rc: TDynRecord); +function TDynRecord.addRecordByType (const atypename: AnsiString; rc: TDynRecord): Boolean; var fld: TDynField; begin @@ -1683,7 +1879,7 @@ begin // first record fld := TDynField.Create(atypename, TDynField.TType.TList); fld.mOwner := mHeaderRec; - mHeaderRec.mFields.append(fld); + mHeaderRec.addField(fld); end; if (fld.mType <> fld.TType.TList) then raise Exception.Create(Format('cannot append record of type ''%s'' due to name conflict with ordinary field', [atypename])); // append @@ -1692,8 +1888,7 @@ begin fld.mRVal := TDynRecList.Create(); fld.mRHash := hashNewStrInt(); end; - fld.mRVal.append(rc); - if (Length(rc.mId) > 0) then fld.mRHash.put(rc.mId, fld.mRVal.count-1); + result := fld.addListItem(rc); end; @@ -1741,6 +1936,81 @@ 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; @@ -1800,10 +2070,14 @@ begin while (pr.tokType <> pr.TTEnd) do begin fld := TDynField.Create(pr); - if hasByName(fld.name) then begin fld.Free(); raise Exception.Create(Format('duplicate field ''%s''', [fld.name])); end; + //if hasByName(fld.name) then begin fld.Free(); raise Exception.Create(Format('duplicate field ''%s''', [fld.name])); end; // append fld.mOwner := self; - mFields.append(fld); + if addFieldChecked(fld) then + begin + fld.Free(); + raise Exception.Create(Format('duplicate field ''%s''', [fld.name])); + end; // done with field end; pr.expectTT(pr.TTEnd); @@ -1961,7 +2235,7 @@ begin // create list for this type fld := TDynField.Create(rec.mName, TDynField.TType.TList); fld.mOwner := self; - mFields.append(fld); + addField(fld); if (bsize > 0) then begin GetMem(buf, bsize); @@ -1973,8 +2247,7 @@ begin rec.mHeaderRec := self; rec.parseBinValue(mst); rec.mId := Format('%s%d', [rec.mName, f]); - fld.mRVal.append(rec); - fld.mRHash.put(rec.mId, fld.mRVal.count-1); + fld.addListItem(rec); //writeln('parsed ''', rec.mId, '''...'); end; end; @@ -2244,12 +2517,14 @@ begin try pr.skipToken(); rec.parseValue(pr); + (* if (Length(rec.mId) > 0) then begin {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF} fld := field[pr.tokStr]; {$IF DEFINED(D2D_DYNREC_PROFILER)}profFieldSearching := curTimeMicro()-stt;{$ENDIF} - if (fld <> nil) and (fld.mRVal <> nil) and (Length(rec.mId) > 0) then + (* + if (fld <> nil) and (fld.mRVal <> nil) then begin {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF} //idtmp := trc.mName+':'+rec.mId; @@ -2258,6 +2533,7 @@ begin {$IF DEFINED(D2D_DYNREC_PROFILER)}profListDupChecking := curTimeMicro()-stt;{$ENDIF} end; end; + *) {$IF DEFINED(D2D_DYNREC_PROFILER)}stt := curTimeMicro();{$ENDIF} addRecordByType(rec.mName, rec); {$IF DEFINED(D2D_DYNREC_PROFILER)}profAddRecByType := curTimeMicro()-stt;{$ENDIF} @@ -2748,4 +3024,19 @@ begin end; +function TDynMapDef.pasdefconst (): AnsiString; +var + ebs: TDynEBS; +begin + result := ''; + result += '// ////////////////////////////////////////////////////////////////////////// //'#10; + result += '// enums and bitsets'#10; + for ebs in ebsTypes do result += #10+ebs.pasdef(); +end; + + +function TDynMapDef.getTrigTypeCount (): Integer; inline; begin result := trigTypes.count; end; +function TDynMapDef.getTrigTypeAt (idx: Integer): TDynRecord; inline; begin if (idx >= 0) and (idx < trigTypes.count) then result := trigTypes[idx] else result := nil; end; + + end.