Skip to content

Commit 610d9f0

Browse files
committed
feat: Support Lists -> Array conversion with type hints
Add support for using Lists as properties, and convert Godot Arrays to Dart Lists durring get / set.
1 parent 9cfe837 commit 610d9f0

2 files changed

Lines changed: 147 additions & 23 deletions

File tree

src/dart/godot_dart/lib/src/extensions/core_extensions.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,30 @@ extension WeakRefExtension on Object {
4747
}
4848
}
4949

50-
extension GDPointerExtension<T> on Pointer {
50+
extension GDPointerExtension on Pointer {
5151
Object? toDart() {
5252
return GDNativeInterface.gdObjectToDartObject(cast());
5353
}
5454
}
55+
56+
extension GDArrayExtensions on Array {
57+
List<T> toDartList<T>() {
58+
final arraySize = size();
59+
final list = <T>[];
60+
for (int i = 0; i < arraySize; i++) {
61+
final element = this[i].as<T>();
62+
if (element is T) {
63+
list.add(element);
64+
}
65+
}
66+
return list;
67+
}
68+
69+
static Array fromList<T>(List<T> list) {
70+
final array = Array();
71+
for (var element in list) {
72+
array.append(Variant(element));
73+
}
74+
return array;
75+
}
76+
}

src/dart/godot_dart_build/lib/src/godot_script_generator.dart

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:analyzer/dart/analysis/results.dart';
44
import 'package:analyzer/dart/ast/ast.dart';
55
import 'package:analyzer/dart/constant/value.dart';
66
import 'package:analyzer/dart/element/element.dart';
7+
import 'package:analyzer/dart/element/nullability_suffix.dart';
78
import 'package:analyzer/dart/element/type.dart';
89
import 'package:build/build.dart';
910
import 'package:code_builder/code_builder.dart' as c;
@@ -295,50 +296,95 @@ class GodotScriptAnnotationGenerator
295296
? field.type
296297
: (field as PropertyAccessorElement).returnType;
297298

298-
buffer.writeln('DartPropertyInfo<$parentType, $type>(');
299+
// Handle lists super spec
300+
bool isList = type.isDartCoreList;
301+
DartType? listType;
302+
if (isList && type is ParameterizedType) {
303+
listType = type.typeArguments.first;
304+
}
305+
306+
buffer
307+
.writeln('DartPropertyInfo<$parentType, ${isList ? 'Array' : type}>(');
299308
buffer.writeln(' name: \'$exportName\',');
300309
buffer.writeln(' typeInfo: ${_typeInfoForType(type)},');
301310

302-
final propertyHint = _getPropertyHint(type);
311+
final propertyHint = _getPropertyHint(type, packageName);
303312
if (propertyHint != null) {
304-
buffer.writeln(' hint: ${propertyHint.toString()},');
305-
buffer.writeln(
306-
' hintString: \'${_getPropertyHintString(type, packageName)}\',');
313+
buffer.writeln(' hint: ${propertyHint.hint.toString()},');
314+
buffer.writeln(' hintString: \'${propertyHint.hintString}\',');
307315
}
308-
buffer.writeln(' getter: (self) => self.${field.name},');
309-
buffer.writeln(' setter: (self, value) => self.${field.name} = value,');
316+
var getterBody = 'self.${field.name}';
317+
var setterBody = 'self.${field.name} = value';
318+
if (isList && listType != null) {
319+
if (listType.nullabilitySuffix != NullabilitySuffix.question) {
320+
log.warning(
321+
'$parentType.$exportName has a non-nullable element type. It is highly recommended'
322+
' that you make List elements nullable in order to make editing them possible.');
323+
}
324+
getterBody = 'GDArrayExtensions.fromList(self.${field.name})';
325+
setterBody = 'self.${field.name} = value.toDartList()';
326+
}
327+
buffer.writeln(' getter: (self) => $getterBody,');
328+
buffer.writeln(' setter: (self, value) => $setterBody,');
310329

311330
buffer.write(')');
312331

313332
return buffer.toString();
314333
}
315334

316-
PropertyHint? _getPropertyHint(DartType type) {
335+
({PropertyHint hint, String hintString})? _getPropertyHint(
336+
DartType type, String packageName) {
317337
final element = type.element;
338+
339+
String getScriptResourceForType(DartType type) {
340+
final element = type.element;
341+
if (element is ClassElement &&
342+
_godotScriptChecker.hasAnnotationOf(element,
343+
throwOnUnresolved: false)) {
344+
final relativeName = element.library.firstFragment.source.fullName
345+
.replaceFirst('/$packageName/', '');
346+
return 'res://src/$relativeName';
347+
}
348+
349+
// Else, return its type
350+
return type.element!.name!;
351+
}
352+
318353
if (element is ClassElement) {
319354
for (final supertype in element.allSupertypes) {
320355
if (supertype.element.name == 'Node') {
321-
return PropertyHint.nodeType;
356+
return (
357+
hint: PropertyHint.nodeType,
358+
hintString: getScriptResourceForType(type)
359+
);
322360
} else if (supertype.element.name == 'Resource') {
323-
return PropertyHint.resourceType;
361+
return (
362+
hint: PropertyHint.resourceType,
363+
hintString: getScriptResourceForType(type)
364+
);
324365
}
325366
}
326367
}
327-
return null;
328-
}
329368

330-
String _getPropertyHintString(DartType type, String packageName) {
331-
final element = type.element;
332-
if (element is ClassElement &&
333-
_godotScriptChecker.hasAnnotationOf(element,
334-
throwOnUnresolved: false)) {
335-
final relativeName = element.library.firstFragment.source.fullName
336-
.replaceFirst('/$packageName/', '');
337-
return 'res://src/$relativeName';
369+
if (type.isDartCoreList) {
370+
final arrayElementType = (type as ParameterizedType).typeArguments.first;
371+
final arrayElementVariantType = _variantTypeForType(arrayElementType);
372+
if (arrayElementVariantType != null) {
373+
final elementHint = _getPropertyHint(arrayElementType, packageName);
374+
if (elementHint != null) {
375+
final hintString =
376+
'${arrayElementVariantType.value}/${elementHint.hint.value}:${elementHint.hintString}';
377+
378+
return (hint: PropertyHint.typeString, hintString: hintString);
379+
} else {
380+
final hintString =
381+
'${arrayElementVariantType.value}/${PropertyHint.none.value}:';
382+
return (hint: PropertyHint.typeString, hintString: hintString);
383+
}
384+
}
338385
}
339386

340-
// Else, return its type
341-
return type.element!.name!;
387+
return null;
342388
}
343389

344390
String _buildRpcMethodInfo(MethodElement method, DartObject rpcAnnotation) {
@@ -372,6 +418,8 @@ class GodotScriptAnnotationGenerator
372418

373419
if (isPrimitive(type)) {
374420
return 'PrimitiveTypeInfo.forType($typeName)!';
421+
} else if (type.isDartCoreList) {
422+
return 'Array.sTypeInfo';
375423
} else if (typeName == 'Variant') {
376424
return 'Variant.sTypeInfo';
377425
} else if (type is VoidType) {
@@ -381,6 +429,60 @@ class GodotScriptAnnotationGenerator
381429
}
382430
}
383431

432+
VariantType? _variantTypeForType(DartType type) {
433+
if (type.isDartCoreInt) {
434+
return VariantType.integer;
435+
} else if (type.isDartCoreDouble) {
436+
return VariantType.float;
437+
} else if (type.isDartCoreBool) {
438+
return VariantType.bool;
439+
} else if (type.isDartCoreString) {
440+
return VariantType.string;
441+
} else if (type.isDartCoreEnum) {
442+
return VariantType.integer;
443+
}
444+
445+
const variantTypeMap = <String, VariantType>{
446+
'Vector2': VariantType.vector2,
447+
'Vector2i': VariantType.vector2i,
448+
'Rect2': VariantType.rect2,
449+
'Rect2i': VariantType.rect2i,
450+
'Vector3': VariantType.vector3,
451+
'Vector3i': VariantType.vector3i,
452+
'Transform2D': VariantType.transform2d,
453+
'Vector4': VariantType.vector4,
454+
'Vector4i': VariantType.vector4i,
455+
'Plane': VariantType.plane,
456+
'Quaternion': VariantType.quaternion,
457+
'AABB': VariantType.aabb,
458+
'Basis': VariantType.basis,
459+
'Transform3D': VariantType.transform3d,
460+
'Projection': VariantType.projection,
461+
'Color': VariantType.color,
462+
'StringName': VariantType.stringName,
463+
'GDStringName': VariantType.string,
464+
'NodePath': VariantType.nodePath,
465+
'RID': VariantType.rid,
466+
'PackedByteArray': VariantType.packedByteArray,
467+
'PackedInt32Array': VariantType.packedInt32Array,
468+
'PackedInt64Array': VariantType.packedFloat64Array,
469+
'PackedFloatArray': VariantType.packedFloat64Array,
470+
'PackedStringArray': VariantType.packedStringArray,
471+
'PackedVector2Array': VariantType.packedVector2Array,
472+
'PackedVector3Array': VariantType.packedVector3Array,
473+
'PackedVector4Array': VariantType.packedVector4Array,
474+
'PackedColor4Array': VariantType.packedColorArray,
475+
// Not supported via conversion
476+
// Object
477+
// Callable
478+
// Signal
479+
// Dictionary
480+
// Array
481+
};
482+
483+
return variantTypeMap[type.element?.name];
484+
}
485+
384486
String _convertVirtualMethodName(String methodName) {
385487
var name = methodName;
386488
if (methodName.startsWith(RegExp('v[A-Z]'))) {

0 commit comments

Comments
 (0)