Skip to content

Commit 619032e

Browse files
committed
feat: Remove need to specify signal name on GodotSignal annotation
Read the AST of the assignment as the Signal name should be available on the creation of the SignalX object.
1 parent 7a0fdb2 commit 619032e

5 files changed

Lines changed: 62 additions & 20 deletions

File tree

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ part 'simple_script.g.dart';
6969
7070
// Generate the boilerplate for this object to be an accessible Godot script
7171
@GodotScript()
72+
@pragma('vm:entry-point')
7273
class SimpleScript extends Sprite2D {
7374
// Return the type info that was generated...
7475
@pragma('vm:entry-point')
@@ -89,6 +90,10 @@ class SimpleScript extends Sprite2D {
8990
@GodotProperty()
9091
int speed = 400
9192
93+
// You can have (mostly) type safe Signals
94+
@GodotSignal()
95+
late final Signal1<Node> onHit = Signal1(this, 'on_hit');
96+
9297
// Overridden virtuals are added automatically via build_runner
9398
@override
9499
void vReady() {}
@@ -141,7 +146,7 @@ class Hud extends CanvasLayer {
141146
//...
142147
143148
144-
@GodotSignal('start_game')
149+
@GodotSignal()
145150
late final Signal0 _startGame = Signal0(this, 'start_game');
146151
}
147152
```
@@ -168,6 +173,8 @@ class MyClass extends Node {
168173

169174
These signal connections are automatically cleaned up if the target supplied to `connect` is removed.
170175

176+
You can also `await` a Signal firing with `SignalX asFuture(this)`.
177+
171178

172179
## Dart classes as Extensions
173180

example/2d_tutorial/src/lib/hud.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Hud extends CanvasLayer {
1414

1515
Hud.withNonNullOwner(super.owner) : super.withNonNullOwner();
1616

17-
@GodotSignal('start_game')
17+
@GodotSignal()
1818
late final Signal0 _startGame = Signal0(this, 'start_game');
1919

2020
@override

example/2d_tutorial/src/lib/player.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Player extends Area2D {
1414

1515
Player.withNonNullOwner(super.owner) : super.withNonNullOwner();
1616

17-
@GodotSignal('hit')
17+
@GodotSignal()
1818
late final Signal0 _hit = Signal0(this, 'hit');
1919

2020
@GodotProperty()

src/dart/godot_dart/lib/src/annotations/godot_script.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ class SignalArgument {
3232
}
3333

3434
/// Annotate a Godot Signal
35+
///
36+
/// Signals must be a variable of one of the `SignalX` types, such as [Signal0], [Signal1], etc.
37+
/// The name of the signal is taken from `name` parameter of the Signal.
3538
@immutable
3639
class GodotSignal {
37-
final String signalName;
38-
39-
const GodotSignal(this.signalName);
40+
const GodotSignal();
4041
}
4142

4243
/// Annotate a field that should be visible to the Godot property inspector

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:mirrors';
22

3+
import 'package:analyzer/dart/analysis/results.dart';
4+
import 'package:analyzer/dart/ast/ast.dart';
35
import 'package:analyzer/dart/constant/value.dart';
46
import 'package:analyzer/dart/element/element.dart';
57
import 'package:analyzer/dart/element/type.dart';
@@ -100,13 +102,13 @@ class GodotScriptAnnotationGenerator
100102
}
101103
for (final getter in element.getters) {
102104
if (_godotPropertyChecker.hasAnnotationOf(getter,
103-
throwOnUnresolved: false)) {
105+
throwOnUnresolved: false)) {
104106
propertyFields.add(getter);
105107
}
106108
}
107109
for (final setter in element.setters) {
108110
if (_godotPropertyChecker.hasAnnotationOf(setter,
109-
throwOnUnresolved: false)) {
111+
throwOnUnresolved: false)) {
110112
log.warning(
111113
'Found `@GodotProperty` on setter `${setter.name}`. `@GodotProperty` should not be used on setters, only getters.');
112114
}
@@ -115,12 +117,16 @@ class GodotScriptAnnotationGenerator
115117
for (final signalField in signalFields) {
116118
final signalAnnotation =
117119
_godotSignalChecker.firstAnnotationOf(signalField);
118-
if (_godotSignalCallableChecker.isAssignableFromType(signalField.type)) {
119-
buffer.write(_buildSignalInfo(signalField, signalAnnotation));
120-
buffer.writeln(',');
120+
if (_godotSignalCallableChecker.isAssignableFromType(signalField.type) &&
121+
signalField.hasInitializer) {
122+
final signalInfo = _buildSignalInfo(signalField, signalAnnotation);
123+
if (signalInfo != null) {
124+
buffer.write(signalInfo);
125+
buffer.writeln(',');
126+
}
121127
} else {
122128
log.severe(
123-
'Use of `@GodotSignal` on invalid field `${signalField.name}. The type must be one of the SignalX classes.');
129+
'Use of `@GodotSignal` on invalid field `${signalField.name}`. The type must be one of the SignalX classes and have a default initialier.');
124130
}
125131
}
126132
buffer.writeln(' ],');
@@ -216,12 +222,40 @@ class GodotScriptAnnotationGenerator
216222
return buffer.toString();
217223
}
218224

219-
String _buildSignalInfo(FieldElement element, DartObject? signalAnnotation) {
225+
AstNode? getAstNodeFromElement(Element element) {
226+
final session = element.session;
227+
final parsedLibResult = session?.getParsedLibraryByElement(element.library!)
228+
as ParsedLibraryResult?;
229+
final fragmentDelcaration =
230+
parsedLibResult?.getFragmentDeclaration(element.firstFragment);
231+
return fragmentDelcaration?.node;
232+
}
233+
234+
String? _buildSignalInfo(
235+
VariableElement element, DartObject? signalAnnotation) {
220236
final buffer = StringBuffer();
221237
buffer.writeln('SignalInfo(');
222238

223-
final reader = ConstantReader(signalAnnotation);
224-
final signalName = reader.read('signalName').stringValue;
239+
// Find the name of the signal through the AST
240+
final node = getAstNodeFromElement(element);
241+
if (node == null) {
242+
log.severe('Could not find AST Node for ${element.name}?');
243+
return null;
244+
}
245+
final constructorNode =
246+
node.childEntities.whereType<MethodInvocation>().firstOrNull;
247+
if (constructorNode == null) {
248+
log.severe(
249+
'Could not find constructor for ${element.name}! Is it initialized properly?');
250+
return null;
251+
}
252+
final signalNameArg = constructorNode.argumentList.arguments[1];
253+
if (signalNameArg is! StringLiteral) {
254+
log.severe('Signal name for ${element.name} must be a StringLiteral!');
255+
return null;
256+
}
257+
258+
final signalName = signalNameArg.stringValue!;
225259
buffer.writeln(' name: \'$signalName\',');
226260

227261
//final signalArguments = reader.read('args').listValue;
@@ -419,14 +453,14 @@ class GodotScriptAnnotationGenerator
419453
..name = 'peerId'
420454
..named = true
421455
..type = c.Reference('int?')));
422-
final requiredParametrs = method.formalParameters.where((e) => !e.isOptional).map(
423-
(e) => c.Parameter((p) => p
456+
final requiredParametrs = method.formalParameters
457+
.where((e) => !e.isOptional)
458+
.map((e) => c.Parameter((p) => p
424459
..named = e.isNamed
425460
..name = e.displayName
426-
..type =
427-
c.Reference(e.type.getDisplayString())));
461+
..type = c.Reference(e.type.getDisplayString())));
428462

429-
return c.Method.returnsVoid((b) => b
463+
return c.Method.returnsVoid((b) => b
430464
..optionalParameters.addAll(optionalParameters)
431465
..requiredParameters.addAll(requiredParametrs)
432466
..name = method.name

0 commit comments

Comments
 (0)