mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-09 10:19:00 +08:00
feat(config): 添加AI-First游戏内容配置系统
- 实现YAML配置文件与JSON Schema结构描述支持 - 提供一对象一文件的目录组织方式 - 集成Source Generator生成配置类型和表包装代码 - 添加VS Code插件支持配置浏览和表单编辑功能 - 实现运行时只读查询和开发期热重载机制 - 支持跨表引用校验和轻量元数据复用 - 添加配置加载异常诊断和批量编辑入口
This commit is contained in:
parent
61cc7eaa6d
commit
ec4e2edeab
@ -101,6 +101,8 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
|
||||
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
|
||||
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
|
||||
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
|
||||
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
|
||||
Assert.That(table.Count, Is.EqualTo(2));
|
||||
Assert.That(table.Get(1).Name, Is.EqualTo("Slime"));
|
||||
Assert.That(table.Get(2).Hp, Is.EqualTo(30));
|
||||
|
||||
@ -132,7 +132,8 @@ public class SchemaConfigGeneratorSnapshotTests
|
||||
"type": "string",
|
||||
"description": "Monster reference id.",
|
||||
"minLength": 2,
|
||||
"maxLength": 32
|
||||
"maxLength": 32,
|
||||
"x-gframework-ref-table": "monster"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +117,7 @@ public sealed partial class MonsterConfig
|
||||
/// <remarks>
|
||||
/// Schema property path: 'phases[].monsterId'.
|
||||
/// Constraints: minLength = 2, maxLength = 32.
|
||||
/// References config table: 'monster'.
|
||||
/// Generated default initializer: = string.Empty;
|
||||
/// </remarks>
|
||||
public string MonsterId { get; set; } = string.Empty;
|
||||
|
||||
@ -9,6 +9,51 @@ namespace GFramework.Game.Config.Generated;
|
||||
/// </summary>
|
||||
public static class MonsterConfigBindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes one schema property that declares <c>x-gframework-ref-table</c> metadata.
|
||||
/// </summary>
|
||||
public readonly struct ReferenceMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes one generated cross-table reference descriptor.
|
||||
/// </summary>
|
||||
/// <param name="displayPath">Schema property path.</param>
|
||||
/// <param name="referencedTableName">Referenced runtime table name.</param>
|
||||
/// <param name="valueSchemaType">Schema scalar type used by the reference value.</param>
|
||||
/// <param name="isCollection">Whether the property stores multiple reference keys.</param>
|
||||
public ReferenceMetadata(
|
||||
string displayPath,
|
||||
string referencedTableName,
|
||||
string valueSchemaType,
|
||||
bool isCollection)
|
||||
{
|
||||
DisplayPath = displayPath ?? throw new global::System.ArgumentNullException(nameof(displayPath));
|
||||
ReferencedTableName = referencedTableName ?? throw new global::System.ArgumentNullException(nameof(referencedTableName));
|
||||
ValueSchemaType = valueSchemaType ?? throw new global::System.ArgumentNullException(nameof(valueSchemaType));
|
||||
IsCollection = isCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schema property path such as <c>dropItems</c> or <c>phases[].monsterId</c>.
|
||||
/// </summary>
|
||||
public string DisplayPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the runtime registration name of the referenced config table.
|
||||
/// </summary>
|
||||
public string ReferencedTableName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the schema scalar type used by the referenced key value.
|
||||
/// </summary>
|
||||
public string ValueSchemaType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the property stores multiple reference keys.
|
||||
/// </summary>
|
||||
public bool IsCollection { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Groups the schema-derived metadata constants so consumer code can reuse one stable entry point.
|
||||
/// </summary>
|
||||
@ -55,6 +100,67 @@ public static class MonsterConfigBindings
|
||||
/// </summary>
|
||||
public const string SchemaRelativePath = Metadata.SchemaRelativePath;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes generated metadata for schema properties that declare <c>x-gframework-ref-table</c>.
|
||||
/// </summary>
|
||||
public static class References
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets generated reference metadata for schema property path 'dropItems'.
|
||||
/// </summary>
|
||||
public static readonly ReferenceMetadata DropItems = new(
|
||||
"dropItems",
|
||||
"item",
|
||||
"string",
|
||||
true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets generated reference metadata for schema property path 'phases[].monsterId'.
|
||||
/// </summary>
|
||||
public static readonly ReferenceMetadata PhasesItemsMonsterId = new(
|
||||
"phases[].monsterId",
|
||||
"monster",
|
||||
"string",
|
||||
false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all generated cross-table reference descriptors for the current schema.
|
||||
/// </summary>
|
||||
public static global::System.Collections.Generic.IReadOnlyList<ReferenceMetadata> All { get; } = global::System.Array.AsReadOnly(new ReferenceMetadata[]
|
||||
{
|
||||
DropItems,
|
||||
PhasesItemsMonsterId,
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Tries to resolve generated reference metadata by schema property path.
|
||||
/// </summary>
|
||||
/// <param name="displayPath">Schema property path.</param>
|
||||
/// <param name="metadata">Resolved generated reference metadata when the path is known; otherwise the default value.</param>
|
||||
/// <returns>True when the schema property path has generated cross-table metadata; otherwise false.</returns>
|
||||
public static bool TryGetByDisplayPath(string displayPath, out ReferenceMetadata metadata)
|
||||
{
|
||||
if (displayPath is null)
|
||||
{
|
||||
throw new global::System.ArgumentNullException(nameof(displayPath));
|
||||
}
|
||||
|
||||
if (string.Equals(displayPath, "dropItems", global::System.StringComparison.Ordinal))
|
||||
{
|
||||
metadata = DropItems;
|
||||
return true;
|
||||
}
|
||||
if (string.Equals(displayPath, "phases[].monsterId", global::System.StringComparison.Ordinal))
|
||||
{
|
||||
metadata = PhasesItemsMonsterId;
|
||||
return true;
|
||||
}
|
||||
|
||||
metadata = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the generated config table using the schema-derived runtime conventions.
|
||||
/// </summary>
|
||||
|
||||
@ -634,6 +634,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
var getMethodName = $"Get{schema.EntityName}Table";
|
||||
var tryGetMethodName = $"TryGet{schema.EntityName}Table";
|
||||
var bindingsClassName = $"{schema.EntityName}ConfigBindings";
|
||||
var referenceSpecs = CollectReferenceSpecs(schema.RootObject).ToArray();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("// <auto-generated />");
|
||||
@ -650,6 +651,59 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
builder.AppendLine($"public static class {bindingsClassName}");
|
||||
builder.AppendLine("{");
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Describes one schema property that declares <c>x-gframework-ref-table</c> metadata.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public readonly struct ReferenceMetadata");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// Initializes one generated cross-table reference descriptor.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"displayPath\">Schema property path.</param>");
|
||||
builder.AppendLine(" /// <param name=\"referencedTableName\">Referenced runtime table name.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"valueSchemaType\">Schema scalar type used by the reference value.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"isCollection\">Whether the property stores multiple reference keys.</param>");
|
||||
builder.AppendLine(" public ReferenceMetadata(");
|
||||
builder.AppendLine(" string displayPath,");
|
||||
builder.AppendLine(" string referencedTableName,");
|
||||
builder.AppendLine(" string valueSchemaType,");
|
||||
builder.AppendLine(" bool isCollection)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
" DisplayPath = displayPath ?? throw new global::System.ArgumentNullException(nameof(displayPath));");
|
||||
builder.AppendLine(
|
||||
" ReferencedTableName = referencedTableName ?? throw new global::System.ArgumentNullException(nameof(referencedTableName));");
|
||||
builder.AppendLine(
|
||||
" ValueSchemaType = valueSchemaType ?? throw new global::System.ArgumentNullException(nameof(valueSchemaType));");
|
||||
builder.AppendLine(" IsCollection = isCollection;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Gets the schema property path such as <c>dropItems</c> or <c>phases[].monsterId</c>.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public string DisplayPath { get; }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// Gets the runtime registration name of the referenced config table.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public string ReferencedTableName { get; }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// Gets the schema scalar type used by the referenced key value.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public string ValueSchemaType { get; }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Gets a value indicating whether the property stores multiple reference keys.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public bool IsCollection { get; }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Groups the schema-derived metadata constants so consumer code can reuse one stable entry point.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
@ -704,6 +758,97 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
builder.AppendLine(" public const string SchemaRelativePath = Metadata.SchemaRelativePath;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Exposes generated metadata for schema properties that declare <c>x-gframework-ref-table</c>.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" public static class References");
|
||||
builder.AppendLine(" {");
|
||||
|
||||
foreach (var referenceSpec in referenceSpecs)
|
||||
{
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
$" /// Gets generated reference metadata for schema property path '{EscapeXmlDocumentation(referenceSpec.DisplayPath)}'.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
$" public static readonly ReferenceMetadata {referenceSpec.MemberName} = new(");
|
||||
builder.AppendLine(
|
||||
$" {SymbolDisplay.FormatLiteral(referenceSpec.DisplayPath, true)},");
|
||||
builder.AppendLine(
|
||||
$" {SymbolDisplay.FormatLiteral(referenceSpec.ReferencedTableName, true)},");
|
||||
builder.AppendLine(
|
||||
$" {SymbolDisplay.FormatLiteral(referenceSpec.ValueSchemaType, true)},");
|
||||
builder.AppendLine(
|
||||
$" {(referenceSpec.IsCollection ? "true" : "false")});");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Gets all generated cross-table reference descriptors for the current schema.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
if (referenceSpecs.Length == 0)
|
||||
{
|
||||
builder.AppendLine(
|
||||
" public static global::System.Collections.Generic.IReadOnlyList<ReferenceMetadata> All { get; } = global::System.Array.Empty<ReferenceMetadata>();");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine(
|
||||
" public static global::System.Collections.Generic.IReadOnlyList<ReferenceMetadata> All { get; } = global::System.Array.AsReadOnly(new ReferenceMetadata[]");
|
||||
builder.AppendLine(" {");
|
||||
foreach (var referenceSpec in referenceSpecs)
|
||||
{
|
||||
builder.AppendLine($" {referenceSpec.MemberName},");
|
||||
}
|
||||
|
||||
builder.AppendLine(" });");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// Tries to resolve generated reference metadata by schema property path.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"displayPath\">Schema property path.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"metadata\">Resolved generated reference metadata when the path is known; otherwise the default value.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns>True when the schema property path has generated cross-table metadata; otherwise false.</returns>");
|
||||
builder.AppendLine(
|
||||
" public static bool TryGetByDisplayPath(string displayPath, out ReferenceMetadata metadata)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (displayPath is null)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(displayPath));");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
|
||||
if (referenceSpecs.Length == 0)
|
||||
{
|
||||
builder.AppendLine(" metadata = default;");
|
||||
builder.AppendLine(" return false;");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var referenceSpec in referenceSpecs)
|
||||
{
|
||||
builder.AppendLine(
|
||||
$" if (string.Equals(displayPath, {SymbolDisplay.FormatLiteral(referenceSpec.DisplayPath, true)}, global::System.StringComparison.Ordinal))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine($" metadata = {referenceSpec.MemberName};");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" metadata = default;");
|
||||
builder.AppendLine(" return false;");
|
||||
}
|
||||
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Registers the generated config table using the schema-derived runtime conventions.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
@ -782,6 +927,78 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收集 schema 中声明的跨表引用元数据,并为生成代码分配稳定成员名。
|
||||
/// </summary>
|
||||
/// <param name="rootObject">根对象模型。</param>
|
||||
/// <returns>生成期引用元数据集合。</returns>
|
||||
private static IEnumerable<GeneratedReferenceSpec> CollectReferenceSpecs(SchemaObjectSpec rootObject)
|
||||
{
|
||||
var memberNameCounts = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var referenceSeed in EnumerateReferenceSeeds(rootObject.Properties))
|
||||
{
|
||||
var baseMemberName = BuildReferenceMemberName(referenceSeed.DisplayPath);
|
||||
if (memberNameCounts.ContainsKey(baseMemberName))
|
||||
{
|
||||
memberNameCounts[baseMemberName]++;
|
||||
baseMemberName =
|
||||
$"{baseMemberName}{memberNameCounts[baseMemberName].ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
memberNameCounts[baseMemberName] = 0;
|
||||
}
|
||||
|
||||
yield return new GeneratedReferenceSpec(
|
||||
baseMemberName,
|
||||
referenceSeed.DisplayPath,
|
||||
referenceSeed.ReferencedTableName,
|
||||
referenceSeed.ValueSchemaType,
|
||||
referenceSeed.IsCollection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归枚举对象树中所有带 ref-table 元数据的字段。
|
||||
/// </summary>
|
||||
/// <param name="properties">对象属性集合。</param>
|
||||
/// <returns>原始引用字段信息。</returns>
|
||||
private static IEnumerable<GeneratedReferenceSeed> EnumerateReferenceSeeds(
|
||||
IEnumerable<SchemaPropertySpec> properties)
|
||||
{
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(property.TypeSpec.RefTableName))
|
||||
{
|
||||
yield return new GeneratedReferenceSeed(
|
||||
property.DisplayPath,
|
||||
property.TypeSpec.RefTableName!,
|
||||
property.TypeSpec.Kind == SchemaNodeKind.Array
|
||||
? property.TypeSpec.ItemTypeSpec?.SchemaType ?? property.TypeSpec.SchemaType
|
||||
: property.TypeSpec.SchemaType,
|
||||
property.TypeSpec.Kind == SchemaNodeKind.Array);
|
||||
}
|
||||
|
||||
if (property.TypeSpec.NestedObject is not null)
|
||||
{
|
||||
foreach (var nestedReference in EnumerateReferenceSeeds(property.TypeSpec.NestedObject.Properties))
|
||||
{
|
||||
yield return nestedReference;
|
||||
}
|
||||
}
|
||||
|
||||
if (property.TypeSpec.ItemTypeSpec?.NestedObject is not null)
|
||||
{
|
||||
foreach (var nestedReference in EnumerateReferenceSeeds(property.TypeSpec.ItemTypeSpec.NestedObject
|
||||
.Properties))
|
||||
{
|
||||
yield return nestedReference;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归生成配置对象类型。
|
||||
/// </summary>
|
||||
@ -1004,6 +1221,28 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
return tokens.Length == 0 ? "Config" : string.Concat(tokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 schema 字段路径转换为可用于生成引用元数据成员的 PascalCase 标识符。
|
||||
/// </summary>
|
||||
/// <param name="displayPath">Schema 字段路径。</param>
|
||||
/// <returns>稳定的成员名。</returns>
|
||||
private static string BuildReferenceMemberName(string displayPath)
|
||||
{
|
||||
var segments = displayPath.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var normalizedSegment = segment
|
||||
.Replace("[]", "Items")
|
||||
.Replace("[", " ")
|
||||
.Replace("]", " ");
|
||||
builder.Append(ToPascalCase(normalizedSegment));
|
||||
}
|
||||
|
||||
return builder.Length == 0 ? "Reference" : builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 AdditionalFiles 诊断创建文件位置。
|
||||
/// </summary>
|
||||
@ -1371,6 +1610,34 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
SchemaObjectSpec? NestedObject,
|
||||
SchemaTypeSpec? ItemTypeSpec);
|
||||
|
||||
/// <summary>
|
||||
/// 生成代码前的跨表引用字段种子信息。
|
||||
/// </summary>
|
||||
/// <param name="DisplayPath">Schema 字段路径。</param>
|
||||
/// <param name="ReferencedTableName">目标表名称。</param>
|
||||
/// <param name="ValueSchemaType">引用值的标量 schema 类型。</param>
|
||||
/// <param name="IsCollection">是否为数组引用。</param>
|
||||
private sealed record GeneratedReferenceSeed(
|
||||
string DisplayPath,
|
||||
string ReferencedTableName,
|
||||
string ValueSchemaType,
|
||||
bool IsCollection);
|
||||
|
||||
/// <summary>
|
||||
/// 已分配稳定成员名的生成期跨表引用信息。
|
||||
/// </summary>
|
||||
/// <param name="MemberName">生成到绑定类中的成员名。</param>
|
||||
/// <param name="DisplayPath">Schema 字段路径。</param>
|
||||
/// <param name="ReferencedTableName">目标表名称。</param>
|
||||
/// <param name="ValueSchemaType">引用值的标量 schema 类型。</param>
|
||||
/// <param name="IsCollection">是否为数组引用。</param>
|
||||
private sealed record GeneratedReferenceSpec(
|
||||
string MemberName,
|
||||
string DisplayPath,
|
||||
string ReferencedTableName,
|
||||
string ValueSchemaType,
|
||||
bool IsCollection);
|
||||
|
||||
/// <summary>
|
||||
/// 属性解析结果包装。
|
||||
/// </summary>
|
||||
|
||||
@ -158,6 +158,21 @@ var schemaPath = MonsterConfigBindings.Metadata.SchemaRelativePath;
|
||||
- 引用目标表需要由同一个 `YamlConfigLoader` 注册,或已存在于当前 `IConfigRegistry`
|
||||
- 热重载中若目标表变更导致依赖表引用失效,会整体回滚受影响表,避免注册表进入不一致状态
|
||||
|
||||
如果你希望在消费者代码里复用这些跨表约定,而不是继续手写字段路径或目标表名,生成的 `*ConfigBindings` 还会暴露引用元数据:
|
||||
|
||||
```csharp
|
||||
var allReferences = MonsterConfigBindings.References.All;
|
||||
|
||||
if (MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out var reference))
|
||||
{
|
||||
Console.WriteLine(reference.ReferencedTableName);
|
||||
Console.WriteLine(reference.ValueSchemaType);
|
||||
Console.WriteLine(reference.IsCollection);
|
||||
}
|
||||
```
|
||||
|
||||
当 schema 中存在具体引用字段时,还可以直接通过生成成员访问,例如 `MonsterConfigBindings.References.DropItems`。
|
||||
|
||||
当前还支持以下“轻量元数据”:
|
||||
|
||||
- `title`:供 VS Code 插件表单和批量编辑入口显示更友好的字段标题
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user