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 localCppCodeFilename => '${stripExtension(_filename)}_base.cpp';
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();'
194 code.writeln('Core* clone() const override;');
197 if (properties.isNotEmpty || _extensionOf == null) {
198 code.writeln('void copy(const ${_name}Base& object) {');
199 for (final property in properties) {
200 code.writeln('m_${property.capitalizedName} = '
201 'object.m_${property.capitalizedName};');
203 if (_extensionOf != null) {
204 code.writeln('${_extensionOf.name}::'
210 code.writeln('bool deserialize(uint16_t propertyKey, '
211 'BinaryReader& reader) override {');
213 code.writeln('switch (propertyKey){');
214 for (final property in properties) {
215 code.writeln('case ${property.name}PropertyKey:');
216 code.writeln('m_${property.capitalizedName} = '
217 '${property.type.runtimeCoreType}::deserialize(reader);');
218 code.writeln('return true;');
221 if (_extensionOf != null) {
222 code.writeln('return ${_extensionOf.name}::'
223 'deserialize(propertyKey, reader); }');
225 code.writeln('return false; }');
229 code.writeln('protected:');
230 if (properties.isNotEmpty) {
231 for (final property in properties) {
232 code.writeln('virtual void ${property.name}Changed() {}');
238 var file = File('$generatedHppPath$localCodeFilename');
239 file.createSync(recursive: true);
242 await _formatter.formatAndGuard('${_name}Base', code.toString());
243 file.writeAsStringSync(formattedCode, flush: true);
245 // See if we need to stub out the concrete version...
246 var concreteFile = File('$concreteHppPath$concreteCodeFilename');
247 if (!concreteFile.existsSync()) {
248 StringBuffer concreteCode = StringBuffer();
249 concreteFile.createSync(recursive: true);
250 concreteCode.writeln('#include "generated/$localCodeFilename"');
251 concreteCode.writeln('#include <stdio.h>');
252 concreteCode.writeln('namespace rive {');
253 concreteCode.writeln('''class $_name : public ${_name}Base {
256 concreteCode.writeln('}');
259 await _formatter.formatAndGuard(_name, concreteCode.toString());
260 concreteFile.writeAsStringSync(formattedCode, flush: true);
263 StringBuffer cppCode = StringBuffer();
264 cppCode.writeln('#include "generated/$localCodeFilename"');
265 cppCode.writeln('#include "$concreteCodeFilename"');
267 cppCode.writeln('using namespace rive;');
269 cppCode.writeln('Core* ${_name}Base::clone() const { '
270 'auto cloned = new $_name(); '
271 'cloned->copy(*this); '
274 var cppFile = File('$generatedCppPath$localCppCodeFilename');
275 cppFile.createSync(recursive: true);
276 var formattedCode = await _formatter.format(cppCode.toString());
277 cppFile.writeAsStringSync(formattedCode, flush: true);
283 return '$_name[${_key?.intValue ?? '-'}]';
286 static const int minPropertyId = 3;
287 static Future<bool> generate() async {
289 bool runGenerator = true;
290 Map<int, Definition> ids = {};
291 Map<int, Property> properties = {};
292 for (final definition in definitions.values) {
293 if (definition._key?.intValue != null) {
294 var other = ids[definition._key.intValue];
296 color('Duplicate type ids for $definition and $other.',
298 runGenerator = false;
300 ids[definition._key.intValue] = definition;
303 for (final property in definition._properties) {
304 if (property.key.isMissing) {
307 var other = properties[property.key.intValue];
310 '''Duplicate field ids for ${property.definition}.$property '''
311 '''and ${other.definition}.$other.''',
313 runGenerator = false;
314 } else if (property.key.intValue < minPropertyId) {
316 '${property.definition}.$property: ids less than '
317 '$minPropertyId are reserved.',
319 runGenerator = false;
321 properties[property.key.intValue] = property;
326 // Find max id, we use this to assign to types that don't have ids yet.
327 int nextFieldId = minPropertyId - 1;
329 for (final definition in definitions.values) {
330 if (definition._key != null &&
331 definition._key.intValue != null &&
332 definition._key.intValue > nextId) {
333 nextId = definition._key.intValue;
335 for (final field in definition._properties) {
337 field.key.intValue != null &&
338 field.key.intValue > nextFieldId) {
339 nextFieldId = field.key.intValue;
345 color('Not running generator due to previous errors.',
346 front: Styles.YELLOW);
350 definitions.removeWhere((key, definition) => definition._editorOnly);
352 // Clear out previous generated code.
353 var dir = Directory(generatedHppPath);
354 if (dir.existsSync()) {
355 dir.deleteSync(recursive: true);
357 dir.createSync(recursive: true);
358 // Generate core context.
360 for (final definition in definitions.values) {
361 await definition.generateCode();
364 StringBuffer ctxCode = StringBuffer('');
365 var includes = <String>{};
366 for (final definition in definitions.values) {
367 includes.add(definition.concreteCodeFilename);
369 var includeList = includes.toList()..sort();
370 for (final include in includeList) {
371 ctxCode.writeln('#include "$include"');
373 ctxCode.writeln('namespace rive {class CoreRegistry {'
375 ctxCode.writeln('static Core* makeCoreInstance(int typeKey) {'
376 'switch(typeKey) {');
377 for (final definition in definitions.values) {
378 if (definition._isAbstract) {
381 ctxCode.writeln('case ${definition.name}Base::typeKey:');
382 ctxCode.writeln('return new ${definition.name}();');
384 ctxCode.writeln('} return nullptr; }');
386 var usedFieldTypes = <FieldType, List<Property>>{};
387 for (final definition in definitions.values) {
388 for (final property in definition.properties) {
389 usedFieldTypes[property.type] ??= [];
390 usedFieldTypes[property.type].add(property);
393 for (final fieldType in usedFieldTypes.keys) {
395 .writeln('static void set${fieldType.capitalizedName}(Core* object, '
396 'int propertyKey, ${fieldType.cppName} value){');
397 ctxCode.writeln('switch (propertyKey) {');
398 var properties = usedFieldTypes[fieldType];
399 for (final property in properties) {
400 ctxCode.writeln('case ${property.definition.name}Base'
401 '::${property.name}PropertyKey:');
402 ctxCode.writeln('object->as<${property.definition.name}Base>()->'
403 '${property.name}(value);');
404 ctxCode.writeln('break;');
406 ctxCode.writeln('}}');
408 for (final fieldType in usedFieldTypes.keys) {
410 'static ${fieldType.cppName} get${fieldType.capitalizedName}('
411 'Core* object, int propertyKey){');
412 ctxCode.writeln('switch (propertyKey) {');
413 var properties = usedFieldTypes[fieldType];
414 for (final property in properties) {
415 ctxCode.writeln('case ${property.definition.name}Base'
416 '::${property.name}PropertyKey:');
417 ctxCode.writeln('return object->as<${property.definition.name}Base>()->'
418 '${property.name}();');
420 ctxCode.writeln('}');
421 ctxCode.writeln('return ${fieldType.defaultValue ?? 'nullptr'};');
422 ctxCode.writeln('}');
425 ctxCode.writeln('static int propertyFieldId(int propertyKey) {');
426 ctxCode.writeln('switch(propertyKey) {');
428 for (final fieldType in usedFieldTypes.keys) {
429 var properties = usedFieldTypes[fieldType];
430 for (final property in properties) {
431 ctxCode.writeln('case ${property.definition.name}Base'
432 '::${property.name}PropertyKey:');
434 ctxCode.writeln('return Core${fieldType.capitalizedName}Type::id;');
437 ctxCode.writeln('default: return -1;}}');
438 /*Core makeCoreInstance(int typeKey) {
440 case KeyedObjectBase.typeKey:
441 return KeyedObject();
442 case KeyedPropertyBase.typeKey:
443 return KeyedProperty();*/
444 // Put our fields in.
445 // var usedFieldTypes = <FieldType>{};
446 // for (final definition in definitions.values) {
447 // for (final property in definition.properties) {
448 // usedFieldTypes.add(property.type);
451 // // Find fields we use.
453 // for (final fieldType in usedFieldTypes) {
454 // ctxCode.writeln('static ${fieldType.runtimeCoreType} '
455 // '${fieldType.uncapitalizedName}Type;');
457 ctxCode.writeln('};}');
459 var output = generatedHppPath;
461 output != null && output.isNotEmpty && output[output.length - 1] == '/'
462 ? output.substring(0, output.length - 1)
465 var file = File('$folder/core_registry.hpp');
466 file.createSync(recursive: true);
469 await _formatter.formatAndGuard('CoreRegistry', ctxCode.toString());
470 file.writeAsStringSync(formattedCode, flush: true);