Skip to content

Commit b632b0d

Browse files
committed
cleanup
1 parent af4aa02 commit b632b0d

14 files changed

Lines changed: 397 additions & 327 deletions

File tree

src/NativeCodeGen.Core/Generation/ILanguageEmitter.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ public interface ILanguageEmitter
8383
/// </summary>
8484
void EmitClassEnd(CodeBuilder cb, string className, ClassKind kind);
8585

86+
/// <summary>
87+
/// Emits a lazy-initialized accessor that caches a wrapper class instance.
88+
/// </summary>
89+
void EmitLazyAccessor(CodeBuilder cb, string className, LazyAccessor accessor);
90+
91+
/// <summary>
92+
/// Emits a native accessor that directly invokes a native function.
93+
/// </summary>
94+
void EmitNativeAccessor(CodeBuilder cb, string className, NativeAccessor accessor);
95+
8696
/// <summary>
8797
/// Emits a constructor for a handle-based class.
8898
/// </summary>
@@ -190,6 +200,59 @@ public interface ILanguageEmitter
190200
void EmitNestedStructAccessor(CodeBuilder cb, string structName, string fieldName, string nestedStructName, int offset, bool isArray, int arraySize, string? comment);
191201
}
192202

203+
/// <summary>
204+
/// Defines a lazy-initialized accessor property.
205+
/// </summary>
206+
public record LazyAccessor(string FieldName, string ReturnType, string InitExpression);
207+
208+
/// <summary>
209+
/// Defines a special accessor that invokes a native directly.
210+
/// </summary>
211+
public record NativeAccessor(string Name, string ReturnType, string Hash, string Description);
212+
213+
/// <summary>
214+
/// Definitions for special class accessors that require hardcoded generation.
215+
/// </summary>
216+
public static class SpecialAccessors
217+
{
218+
/// <summary>
219+
/// Lazy accessors for Ped class (task and weapon wrappers).
220+
/// </summary>
221+
public static readonly LazyAccessor[] PedAccessors =
222+
[
223+
new("_task", "PedTask", "PedTask"),
224+
new("_weapon", "Weapon", "Weapon")
225+
];
226+
227+
/// <summary>
228+
/// Lazy accessors for Vehicle class (task wrapper).
229+
/// </summary>
230+
public static readonly LazyAccessor[] VehicleAccessors =
231+
[
232+
new("_task", "VehicleTask", "VehicleTask")
233+
];
234+
235+
/// <summary>
236+
/// Native accessor for Player.ServerId.
237+
/// </summary>
238+
public static readonly NativeAccessor PlayerServerId = new(
239+
"ServerId",
240+
"number",
241+
SpecialNatives.GetPlayerServerId,
242+
"Gets the player's server ID. In multiplayer, this is the player's unique server-side identifier."
243+
);
244+
245+
/// <summary>
246+
/// Native accessor for Entity.NetworkId.
247+
/// </summary>
248+
public static readonly NativeAccessor EntityNetworkId = new(
249+
"NetworkId",
250+
"number",
251+
SpecialNatives.NetworkGetNetworkIdFromEntity,
252+
"Gets the network ID of this entity for network synchronization."
253+
);
254+
}
255+
193256
/// <summary>
194257
/// Kind of class being generated.
195258
/// </summary>

src/NativeCodeGen.Core/Generation/ITypeMapper.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
namespace NativeCodeGen.Core.Generation;
44

5+
/// <summary>
6+
/// Complete information for generating DataView field accessors.
7+
/// </summary>
8+
public record DataViewAccessorInfo(
9+
string LanguageType,
10+
string GetMethod,
11+
string SetMethod,
12+
string EndianArg,
13+
bool IsBool
14+
);
15+
516
/// <summary>
617
/// Maps C types to target language types.
718
/// </summary>
@@ -56,6 +67,11 @@ public interface ITypeMapper
5667
/// </summary>
5768
(string LanguageType, string GetMethod, string SetMethod) GetDataViewAccessor(TypeInfo type);
5869

70+
/// <summary>
71+
/// Gets complete DataView accessor info including endianness handling.
72+
/// </summary>
73+
DataViewAccessorInfo GetDataViewAccessorInfo(TypeInfo type);
74+
5975
/// <summary>
6076
/// Gets the mapped type for a pointer/output parameter (the type that will be returned).
6177
/// E.g., "int*" -> "number", "float*" -> "number", "Vector3*" -> "Vector3"

src/NativeCodeGen.Core/Generation/NativeClassifier.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Frozen;
22
using NativeCodeGen.Core.Models;
3+
using NativeCodeGen.Core.Utilities;
34
using NativeCodeGen.Core.Parsing;
45

56
namespace NativeCodeGen.Core.Generation;
@@ -117,7 +118,7 @@ public ClassifiedNatives Classify(NativeDatabase db)
117118
{
118119
var handleType = firstParam.Type.Name;
119120

120-
if (native.Namespace.Equals("TASK", StringComparison.OrdinalIgnoreCase))
121+
if (native.Namespace.EqualsIgnoreCase("TASK"))
121122
{
122123
return handleType switch
123124
{
@@ -128,14 +129,14 @@ public ClassifiedNatives Classify(NativeDatabase db)
128129
};
129130
}
130131

131-
if (native.Namespace.Equals("WEAPON", StringComparison.OrdinalIgnoreCase) && handleType == "Ped")
132+
if (native.Namespace.EqualsIgnoreCase("WEAPON") && handleType == "Ped")
132133
{
133134
return "Weapon";
134135
}
135136

136137
if (TypeToNamespace.TryGetValue(handleType, out var expectedNs))
137138
{
138-
if (native.Namespace.Equals(expectedNs, StringComparison.OrdinalIgnoreCase))
139+
if (native.Namespace.EqualsIgnoreCase(expectedNs))
139140
{
140141
return TypeInfo.NormalizeHandleName(handleType);
141142
}
@@ -147,7 +148,7 @@ public ClassifiedNatives Classify(NativeDatabase db)
147148
}
148149
}
149150

150-
if (native.Namespace.Equals("STREAMING", StringComparison.OrdinalIgnoreCase) &&
151+
if (native.Namespace.EqualsIgnoreCase("STREAMING") &&
151152
firstParam.Type.Category == TypeCategory.Hash)
152153
{
153154
var upperName = native.Name.ToUpperInvariant();

src/NativeCodeGen.Core/Generation/RawNativeBuilder.cs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public static void EmitTypeScriptAliases(CodeBuilder cb)
7272
cb.AppendLine($"const {cfg.PointerIntInitAlias} = Citizen.pointerValueIntInitialized;");
7373
cb.AppendLine($"const {cfg.PointerFloatInitAlias} = Citizen.pointerValueFloatInitialized;");
7474
cb.AppendLine($"const {cfg.FloatWrapperAlias} = (v: number) => v + 0.0000000001;");
75-
cb.AppendLine($"const {cfg.HashWrapperAlias} = (v: string | number) => (typeof v === 'string' ? GetHashKey(v) : v) & 0xFFFFFFFF;");
75+
cb.AppendLine($"const {cfg.HashWrapperAlias} = (v: string | number) => (typeof v === 'string' ? GetHashKey(v) : v) & {SpecialNatives.HashMask};");
7676
cb.AppendLine();
7777
}
7878

@@ -92,7 +92,7 @@ public static void EmitLuaAliases(CodeBuilder cb)
9292
cb.AppendLine($"local {cfg.PointerVectorAlias} = Citizen.PointerValueVector");
9393
cb.AppendLine($"local {cfg.PointerIntInitAlias} = Citizen.PointerValueIntInitialized");
9494
cb.AppendLine($"local {cfg.PointerFloatInitAlias} = Citizen.PointerValueFloatInitialized");
95-
cb.AppendLine($"local {cfg.HashWrapperAlias} = function(v) return (type(v) == 'string' and GetHashKey(v) or v) & 0xFFFFFFFF end");
95+
cb.AppendLine($"local {cfg.HashWrapperAlias} = function(v) return (type(v) == 'string' and GetHashKey(v) or v) & {SpecialNatives.HashMask} end");
9696
cb.AppendLine();
9797
}
9898

@@ -262,19 +262,8 @@ private string BuildParamList(List<NativeParameter> inputParams)
262262
}
263263
}
264264

265-
private string MapDefaultValue(string value, TypeInfo type)
266-
{
267-
// Convert C-style boolean literals to TypeScript/Lua
268-
if (type.IsBool)
269-
{
270-
return value.Equals("true", StringComparison.OrdinalIgnoreCase) ||
271-
value.Equals("TRUE", StringComparison.OrdinalIgnoreCase) ||
272-
value == "1"
273-
? "true"
274-
: "false";
275-
}
276-
return value;
277-
}
265+
private string MapDefaultValue(string value, TypeInfo type) =>
266+
Utilities.DefaultValueMapper.MapDefaultValue(value, type);
278267

279268
private string BuildReturnType(TypeInfo returnType, List<NativeParameter> outputParams)
280269
{
@@ -385,7 +374,7 @@ private void EmitInvokeNative(NativeDefinition native, List<NativeParameter> out
385374
// Wrap hash returns to ensure unsigned (simple case with no output params)
386375
if (hasHashReturn)
387376
{
388-
invokeExpr = $"({invokeExpr}) & 0xFFFFFFFF";
377+
invokeExpr = $"({invokeExpr}) & {SpecialNatives.HashMask}";
389378
}
390379

391380
_cb.AppendLine($"return {invokeExpr};");
@@ -419,7 +408,7 @@ private void EmitComplexReturn(NativeDefinition native, List<NativeParameter> ou
419408
var expr = $"result[{idx}]";
420409
// Wrap hash return to ensure unsigned
421410
if (native.ReturnType.Category == TypeCategory.Hash)
422-
expr = $"{expr} & 0xFFFFFFFF";
411+
expr = $"{expr} & {SpecialNatives.HashMask}";
423412
parts.Add(expr);
424413
idx++;
425414
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace NativeCodeGen.Core.Generation;
2+
3+
/// <summary>
4+
/// Contains native function hashes used for special built-in methods.
5+
/// These are CFX/RedM natives used by generated entity classes.
6+
/// </summary>
7+
public static class SpecialNatives
8+
{
9+
/// <summary>
10+
/// GET_PLAYER_SERVER_ID (CFX native) - Gets a player's server ID.
11+
/// Used by Player.ServerId / Player:getServerId()
12+
/// </summary>
13+
public const string GetPlayerServerId = "0x4D97BCC7";
14+
15+
/// <summary>
16+
/// NETWORK_GET_NETWORK_ID_FROM_ENTITY - Gets an entity's network ID.
17+
/// Used by Entity.NetworkId / Entity:getNetworkId()
18+
/// </summary>
19+
public const string NetworkGetNetworkIdFromEntity = "0xA11700682F3AD45C";
20+
21+
/// <summary>
22+
/// NETWORK_DOES_ENTITY_EXIST_WITH_NETWORK_ID - Checks if an entity exists with a given network ID.
23+
/// Used by Entity.fromNetworkId() / Entity.fromNetworkId()
24+
/// </summary>
25+
public const string NetworkDoesEntityExistWithNetworkId = "0x18A47D074708FD68";
26+
27+
/// <summary>
28+
/// NETWORK_GET_ENTITY_FROM_NETWORK_ID - Gets an entity handle from its network ID.
29+
/// Used by Entity.fromNetworkId() / Entity.fromNetworkId()
30+
/// </summary>
31+
public const string NetworkGetEntityFromNetworkId = "0xCE4E5D9B0A4FF560";
32+
33+
/// <summary>
34+
/// Mask for ensuring hash values are unsigned 32-bit integers.
35+
/// </summary>
36+
public const string HashMask = "0xFFFFFFFF";
37+
}

src/NativeCodeGen.Core/Parsing/CLexer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using NativeCodeGen.Core.Utilities;
2+
13
namespace NativeCodeGen.Core.Parsing;
24

35
public enum CTokenType
@@ -222,7 +224,7 @@ private CToken ReadNumber(int startLine, int startCol)
222224
_position += 2;
223225
_column += 2;
224226
// Read hex digits
225-
while (_position < _input.Length && IsHexDigit(_input[_position]))
227+
while (_position < _input.Length && LexerUtilities.IsHexDigit(_input[_position]))
226228
{
227229
_position++;
228230
_column++;
@@ -265,6 +267,4 @@ private CToken ReadIdentifier(int startLine, int startCol)
265267
};
266268
}
267269

268-
private static bool IsHexDigit(char c) =>
269-
char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
270270
}

src/NativeCodeGen.Core/Parsing/MdxParser.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Markdig.Syntax;
33
using Markdig.Syntax.Inlines;
44
using NativeCodeGen.Core.Models;
5+
using NativeCodeGen.Core.Utilities;
56

67
namespace NativeCodeGen.Core.Parsing;
78

@@ -70,12 +71,12 @@ public ParseResult<NativeDefinition> Parse(string content, string filePath)
7071
else if (AllowedSections.Contains(headingText))
7172
{
7273
currentSection = headingText;
73-
if (headingText.Equals("Parameters", StringComparison.OrdinalIgnoreCase))
74+
if (headingText.EqualsIgnoreCase("Parameters"))
7475
foundParameters = true;
75-
else if (headingText.Equals("Return value", StringComparison.OrdinalIgnoreCase))
76+
else if (headingText.EqualsIgnoreCase("Return value"))
7677
foundReturnValue = true;
7778
}
78-
else if (!headingText.Equals(native.Name, StringComparison.OrdinalIgnoreCase))
79+
else if (!headingText.EqualsIgnoreCase(native.Name))
7980
{
8081
// Unknown section
8182
result.Errors.Add(new ParseError
@@ -97,7 +98,7 @@ public ParseResult<NativeDefinition> Parse(string content, string filePath)
9798
if (hashLine.StartsWith("//"))
9899
{
99100
var hashPart = hashLine[2..].Trim();
100-
if (hashPart.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
101+
if (hashPart.StartsWithIgnoreCase("0x"))
101102
{
102103
native.Hash = hashPart;
103104
}
@@ -136,7 +137,7 @@ public ParseResult<NativeDefinition> Parse(string content, string filePath)
136137
native.Parameters = parameters;
137138

138139
// Verify name matches heading
139-
if (!name.Equals(native.Name, StringComparison.OrdinalIgnoreCase))
140+
if (!name.EqualsIgnoreCase(native.Name))
140141
{
141142
result.Warnings.Add(new ParseWarning
142143
{
@@ -193,18 +194,18 @@ public ParseResult<NativeDefinition> Parse(string content, string filePath)
193194
}
194195
break;
195196

196-
case ListBlock listBlock when currentSection?.Equals("Parameters", StringComparison.OrdinalIgnoreCase) == true:
197+
case ListBlock listBlock when currentSection?.EqualsIgnoreCase("Parameters") == true:
197198
ParseParameterList(listBlock, native, parameterDescriptions, out var documentedParamOrder, filePath, frontmatterEndLine);
198199

199200
// Validate parameter count, names, and order
200201
ValidateParameters(native.Parameters, documentedParamOrder, result, filePath, frontmatterEndLine + listBlock.Line);
201202
break;
202203

203-
case ParagraphBlock returnParagraph when currentSection?.Equals("Return value", StringComparison.OrdinalIgnoreCase) == true:
204+
case ParagraphBlock returnParagraph when currentSection?.EqualsIgnoreCase("Return value") == true:
204205
native.ReturnDescription = GetParagraphText(returnParagraph);
205206
break;
206207

207-
case FencedCodeBlock exampleBlock when currentSection?.Equals("Examples", StringComparison.OrdinalIgnoreCase) == true:
208+
case FencedCodeBlock exampleBlock when currentSection?.EqualsIgnoreCase("Examples") == true:
208209
var exampleCode = exampleBlock.Lines.ToString().Trim();
209210
if (!string.IsNullOrWhiteSpace(exampleCode))
210211
{

src/NativeCodeGen.Core/Parsing/SignatureLexer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using NativeCodeGen.Core.Utilities;
2+
13
namespace NativeCodeGen.Core.Parsing;
24

35
public enum TokenType
@@ -92,7 +94,7 @@ private void SkipWhitespace()
9294
{
9395
_position += 2;
9496
_column += 2;
95-
while (_position < _input.Length && IsHexDigit(_input[_position]))
97+
while (_position < _input.Length && LexerUtilities.IsHexDigit(_input[_position]))
9698
{
9799
_position++;
98100
_column++;
@@ -241,6 +243,4 @@ private Token ReadNumber(int startPos, int startLine, int startCol)
241243
};
242244
}
243245

244-
private static bool IsHexDigit(char c) =>
245-
char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
246246
}

src/NativeCodeGen.Core/TypeSystem/TypeMapperBase.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ public virtual (string LanguageType, string GetMethod, string SetMethod) GetData
319319
};
320320
}
321321

322+
public virtual DataViewAccessorInfo GetDataViewAccessorInfo(TypeInfo type)
323+
{
324+
var (langType, getMethod, setMethod) = GetDataViewAccessor(type);
325+
var needsEndian = StructLayoutCalculator.NeedsEndianArgument(type);
326+
var endianArg = needsEndian ? ", true" : "";
327+
return new DataViewAccessorInfo(langType, getMethod, setMethod, endianArg, type.IsBool);
328+
}
329+
322330
public virtual string GetOutputParamType(TypeInfo type)
323331
{
324332
if (type.IsVector3)

0 commit comments

Comments
 (0)