4 import 'package:colorize/colorize.dart';
5 // import 'package:core_generator/src/field_type.dart';
6 import 'package:core_generator/src/comment.dart';
7 import 'package:core_generator/src/field_type.dart';
8 import 'package:core_generator/src/key.dart';
9 import 'package:core_generator/src/cpp_formatter.dart';
10 import 'package:core_generator/src/property.dart';
11 import 'package:core_generator/src/configuration.dart';
13 String stripExtension(String filename) {
14 var index = filename.lastIndexOf('.');
15 return index == -1 ? filename : filename.substring(0, index);
19 static final Map<String, Definition> definitions = <String, Definition>{};
20 final String _filename;
21 static final _formatter = CppFormatter();
24 final List<Property> _properties = [];
26 List<Property> get properties => _properties
27 .where((property) => property.isRuntime)
28 .toList(growable: false);
30 Definition _extensionOf;
32 bool _isAbstract = false;
33 bool _editorOnly = false;
34 factory Definition(String filename) {
35 var definition = definitions[filename];
36 if (definition != null) {
40 var file = File(defsPath + filename);
41 var contents = file.readAsStringSync();
42 Map<String, dynamic> definitionData;
44 dynamic parsedJson = json.decode(contents);
45 if (parsedJson is Map<String, dynamic>) {
46 definitionData = parsedJson;
48 } on FormatException catch (error) {
49 color('Invalid json data in $filename: $error', front: Styles.RED);
52 definitions[filename] =
53 definition = Definition.fromFilename(filename, definitionData);
56 Definition.fromFilename(this._filename, Map<String, dynamic> data) {
57 dynamic extendsFilename = data['extends'];
58 if (extendsFilename is String) {
59 _extensionOf = Definition(extendsFilename);
61 dynamic nameValue = data['name'];
62 if (nameValue is String) {
65 dynamic abstractValue = data['abstract'];
66 if (abstractValue is bool) {
67 _isAbstract = abstractValue;
69 dynamic editorOnlyValue = data['editorOnly'];
70 if (editorOnlyValue is bool) {
71 _editorOnly = editorOnlyValue;
73 _key = Key.fromJSON(data['key']) ?? Key.forDefinition(this);
75 dynamic properties = data['properties'];
76 if (properties is Map<String, dynamic>) {
77 for (final MapEntry<String, dynamic> entry in properties.entries) {
78 if (entry.value is Map<String, dynamic>) {
80 Property(this, entry.key, entry.value as Map<String, dynamic>);
81 if (property == null) {
84 _properties.add(property);
89 String get localFilename => _filename.indexOf(defsPath) == 0
90 ? _filename.substring(defsPath.length)
93 String get name => _name;
95 String get localCodeFilename => '${stripExtension(_filename)}_base.hpp';
96 String get concreteCodeFilename => '${stripExtension(_filename)}.hpp';
97 String get codeFilename => 'lib/src/generated/$localCodeFilename';
99 /// Generates cpp header code based on the Definition
100 Future<void> generateCode() async {
101 bool defineContextExtension = _extensionOf?._name == null;
102 StringBuffer code = StringBuffer();
104 var includes = <String>{
105 defineContextExtension ? 'core.hpp' : _extensionOf.concreteCodeFilename
107 for (final property in properties) {
108 if (property.type.include != null) {
109 includes.add(property.type.include);
112 'core/field_types/' + property.type.snakeRuntimeCoreName + '.hpp');
115 var sortedIncludes = includes.toList()..sort();
116 for (final include in sortedIncludes) {
117 code.write('#include ');
118 if (include[0] == '<') {
121 code.write('\"$include\"');
126 code.writeln('namespace rive {');
127 var superTypeName = defineContextExtension ? 'Core' : _extensionOf?._name;
128 code.writeln('class ${_name}Base : public $superTypeName {');
130 code.writeln('protected:');
131 code.writeln('typedef $superTypeName Super;');
132 code.writeln('public:');
133 code.writeln('static const uint16_t typeKey = ${_key.intValue};\n');
136 'Helper to quickly determine if a core object extends another '
137 'without RTTI at runtime.',
139 code.writeln('bool isTypeOf(uint16_t typeKey) const override {');
141 code.writeln('switch(typeKey) {');
142 code.writeln('case ${_name}Base::typeKey:');
143 for (var p = _extensionOf; p != null; p = p._extensionOf) {
144 code.writeln('case ${p._name}Base::typeKey:');
146 code.writeln('return true;');
147 code.writeln('default: return false;}');
151 code.writeln('uint16_t coreType() const override { return typeKey; }\n');
152 if (properties.isNotEmpty) {
153 for (final property in properties) {
154 code.writeln('static const uint16_t ${property.name}PropertyKey = '
155 '${property.key.intValue};');
157 code.writeln('private:');
160 for (final property in properties) {
161 code.writeln('${property.type.cppName} m_${property.capitalizedName}');
163 var initialize = property.initialValueRuntime ??
164 property.initialValue ??
165 property.type.defaultValue;
166 if (initialize != null) {
167 code.write(' = ${property.type.convertCpp(initialize)}');
172 // Write getter/setters.
173 code.writeln('public:');
174 for (final property in properties) {
176 'inline ${property.type.cppGetterName} ${property.name}() const ' +
177 (property.isGetOverride ? 'override' : '') +
178 '{ return m_${property.capitalizedName}; }');
180 code.writeln('void ${property.name}(${property.type.cppName} value) ' +
181 (property.isSetOverride ? 'override' : '') +
183 'if(m_${property.capitalizedName} == value)'
185 'm_${property.capitalizedName} = value;'
186 '${property.name}Changed();'
193 if (properties.isNotEmpty || _extensionOf == null) {
194 code.writeln('bool deserialize(uint16_t propertyKey, '
195 'BinaryReader& reader) override {');
197 code.writeln('switch (propertyKey){');
198 for (final property in properties) {
199 code.writeln('case ${property.name}PropertyKey:');
200 code.writeln('m_${property.capitalizedName} = '
201 '${property.type.runtimeCoreType}::deserialize(reader);');
202 code.writeln('return true;');
205 if (_extensionOf != null) {
206 code.writeln('return ${_extensionOf.name}::'
207 'deserialize(propertyKey, reader); }');
209 code.writeln('return false; }');
213 code.writeln('protected:');
214 if (properties.isNotEmpty) {
215 for (final property in properties) {
216 code.writeln('virtual void ${property.name}Changed() {}');
222 var file = File('$generatedHppPath$localCodeFilename');
223 file.createSync(recursive: true);
226 await _formatter.formatAndGuard('${_name}Base', code.toString());
227 file.writeAsStringSync(formattedCode, flush: true);
229 // See if we need to stub out the concrete version...
230 var concreteFile = File('$concreteHppPath$concreteCodeFilename');
231 if (!concreteFile.existsSync()) {
232 StringBuffer concreteCode = StringBuffer();
233 concreteFile.createSync(recursive: true);
234 concreteCode.writeln('#include "generated/$localCodeFilename"');
235 concreteCode.writeln('#include <stdio.h>');
236 concreteCode.writeln('namespace rive {');
237 concreteCode.writeln('''class $_name : public ${_name}Base {
240 concreteCode.writeln('}');
243 await _formatter.formatAndGuard(_name, concreteCode.toString());
244 concreteFile.writeAsStringSync(formattedCode, flush: true);
250 return '$_name[${_key?.intValue ?? '-'}]';
253 static const int minPropertyId = 3;
254 static Future<bool> generate() async {
256 bool runGenerator = true;
257 Map<int, Definition> ids = {};
258 Map<int, Property> properties = {};
259 for (final definition in definitions.values) {
260 if (definition._key?.intValue != null) {
261 var other = ids[definition._key.intValue];
263 color('Duplicate type ids for $definition and $other.',
265 runGenerator = false;
267 ids[definition._key.intValue] = definition;
270 for (final property in definition._properties) {
271 if (property.key.isMissing) {
274 var other = properties[property.key.intValue];
277 '''Duplicate field ids for ${property.definition}.$property '''
278 '''and ${other.definition}.$other.''',
280 runGenerator = false;
281 } else if (property.key.intValue < minPropertyId) {
283 '${property.definition}.$property: ids less than '
284 '$minPropertyId are reserved.',
286 runGenerator = false;
288 properties[property.key.intValue] = property;
293 // Find max id, we use this to assign to types that don't have ids yet.
294 int nextFieldId = minPropertyId - 1;
296 for (final definition in definitions.values) {
297 if (definition._key != null &&
298 definition._key.intValue != null &&
299 definition._key.intValue > nextId) {
300 nextId = definition._key.intValue;
302 for (final field in definition._properties) {
304 field.key.intValue != null &&
305 field.key.intValue > nextFieldId) {
306 nextFieldId = field.key.intValue;
312 color('Not running generator due to previous errors.',
313 front: Styles.YELLOW);
317 definitions.removeWhere((key, definition) => definition._editorOnly);
319 // Clear out previous generated code.
320 var dir = Directory(generatedHppPath);
321 if (dir.existsSync()) {
322 dir.deleteSync(recursive: true);
324 dir.createSync(recursive: true);
325 // Generate core context.
327 for (final definition in definitions.values) {
328 await definition.generateCode();
331 StringBuffer ctxCode = StringBuffer('');
332 var includes = <String>{};
333 for (final definition in definitions.values) {
334 includes.add(definition.concreteCodeFilename);
336 var includeList = includes.toList()..sort();
337 for (final include in includeList) {
338 ctxCode.writeln('#include "$include"');
340 ctxCode.writeln('namespace rive {class CoreRegistry {'
342 ctxCode.writeln('static Core* makeCoreInstance(int typeKey) {'
343 'switch(typeKey) {');
344 for (final definition in definitions.values) {
345 if (definition._isAbstract) {
348 ctxCode.writeln('case ${definition.name}Base::typeKey:');
349 ctxCode.writeln('return new ${definition.name}();');
351 ctxCode.writeln('} return nullptr; }');
353 var usedFieldTypes = <FieldType, List<Property>>{};
354 for (final definition in definitions.values) {
355 for (final property in definition.properties) {
356 usedFieldTypes[property.type] ??= [];
357 usedFieldTypes[property.type].add(property);
360 for (final fieldType in usedFieldTypes.keys) {
362 .writeln('static void set${fieldType.capitalizedName}(Core* object, '
363 'int propertyKey, ${fieldType.cppName} value){');
364 ctxCode.writeln('switch (propertyKey) {');
365 var properties = usedFieldTypes[fieldType];
366 for (final property in properties) {
367 ctxCode.writeln('case ${property.definition.name}Base'
368 '::${property.name}PropertyKey:');
369 ctxCode.writeln('object->as<${property.definition.name}Base>()->'
370 '${property.name}(value);');
371 ctxCode.writeln('break;');
373 ctxCode.writeln('}}');
375 for (final fieldType in usedFieldTypes.keys) {
377 'static ${fieldType.cppName} get${fieldType.capitalizedName}('
378 'Core* object, int propertyKey){');
379 ctxCode.writeln('switch (propertyKey) {');
380 var properties = usedFieldTypes[fieldType];
381 for (final property in properties) {
382 ctxCode.writeln('case ${property.definition.name}Base'
383 '::${property.name}PropertyKey:');
384 ctxCode.writeln('return object->as<${property.definition.name}Base>()->'
385 '${property.name}();');
387 ctxCode.writeln('}');
388 ctxCode.writeln('return ${fieldType.defaultValue ?? 'nullptr'};');
389 ctxCode.writeln('}');
392 ctxCode.writeln('static int propertyFieldId(int propertyKey) {');
393 ctxCode.writeln('switch(propertyKey) {');
395 for (final fieldType in usedFieldTypes.keys) {
396 var properties = usedFieldTypes[fieldType];
397 for (final property in properties) {
398 ctxCode.writeln('case ${property.definition.name}Base'
399 '::${property.name}PropertyKey:');
401 ctxCode.writeln('return Core${fieldType.capitalizedName}Type::id;');
404 ctxCode.writeln('default: return -1;}}');
405 /*Core makeCoreInstance(int typeKey) {
407 case KeyedObjectBase.typeKey:
408 return KeyedObject();
409 case KeyedPropertyBase.typeKey:
410 return KeyedProperty();*/
411 // Put our fields in.
412 // var usedFieldTypes = <FieldType>{};
413 // for (final definition in definitions.values) {
414 // for (final property in definition.properties) {
415 // usedFieldTypes.add(property.type);
418 // // Find fields we use.
420 // for (final fieldType in usedFieldTypes) {
421 // ctxCode.writeln('static ${fieldType.runtimeCoreType} '
422 // '${fieldType.uncapitalizedName}Type;');
424 ctxCode.writeln('};}');
426 var output = generatedHppPath;
428 output != null && output.isNotEmpty && output[output.length - 1] == '/'
429 ? output.substring(0, output.length - 1)
432 var file = File('$folder/core_registry.hpp');
433 file.createSync(recursive: true);
436 await _formatter.formatAndGuard('CoreRegistry', ctxCode.toString());
437 file.writeAsStringSync(formattedCode, flush: true);