d1a2042c2bb2e459c6e0aade675da8763250917a
[platform/core/uifw/rive-tizen.git] / submodule / dev / core_generator / lib / src / definition.dart
1 import 'dart:convert';
2 import 'dart:io';
3
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';
12
13 String stripExtension(String filename) {
14   var index = filename.lastIndexOf('.');
15   return index == -1 ? filename : filename.substring(0, index);
16 }
17
18 class Definition {
19   static final Map<String, Definition> definitions = <String, Definition>{};
20   final String _filename;
21   static final _formatter = CppFormatter();
22
23   String _name;
24   final List<Property> _properties = [];
25
26   List<Property> get properties => _properties
27       .where((property) => property.isRuntime)
28       .toList(growable: false);
29
30   Definition _extensionOf;
31   Key _key;
32   bool _isAbstract = false;
33   bool _editorOnly = false;
34   factory Definition(String filename) {
35     var definition = definitions[filename];
36     if (definition != null) {
37       return definition;
38     }
39
40     var file = File(defsPath + filename);
41     var contents = file.readAsStringSync();
42     Map<String, dynamic> definitionData;
43     try {
44       dynamic parsedJson = json.decode(contents);
45       if (parsedJson is Map<String, dynamic>) {
46         definitionData = parsedJson;
47       }
48     } on FormatException catch (error) {
49       color('Invalid json data in $filename: $error', front: Styles.RED);
50       return null;
51     }
52     definitions[filename] =
53         definition = Definition.fromFilename(filename, definitionData);
54     return definition;
55   }
56   Definition.fromFilename(this._filename, Map<String, dynamic> data) {
57     dynamic extendsFilename = data['extends'];
58     if (extendsFilename is String) {
59       _extensionOf = Definition(extendsFilename);
60     }
61     dynamic nameValue = data['name'];
62     if (nameValue is String) {
63       _name = nameValue;
64     }
65     dynamic abstractValue = data['abstract'];
66     if (abstractValue is bool) {
67       _isAbstract = abstractValue;
68     }
69     dynamic editorOnlyValue = data['editorOnly'];
70     if (editorOnlyValue is bool) {
71       _editorOnly = editorOnlyValue;
72     }
73     _key = Key.fromJSON(data['key']) ?? Key.forDefinition(this);
74
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>) {
79           var property =
80               Property(this, entry.key, entry.value as Map<String, dynamic>);
81           if (property == null) {
82             continue;
83           }
84           _properties.add(property);
85         }
86       }
87     }
88   }
89   String get localFilename => _filename.indexOf(defsPath) == 0
90       ? _filename.substring(defsPath.length)
91       : _filename;
92
93   String get name => _name;
94
95   String get localCodeFilename => '${stripExtension(_filename)}_base.hpp';
96   String get concreteCodeFilename => '${stripExtension(_filename)}.hpp';
97   String get codeFilename => 'lib/src/generated/$localCodeFilename';
98
99   /// Generates cpp header code based on the Definition
100   Future<void> generateCode() async {
101     bool defineContextExtension = _extensionOf?._name == null;
102     StringBuffer code = StringBuffer();
103
104     var includes = <String>{
105       defineContextExtension ? 'core.hpp' : _extensionOf.concreteCodeFilename
106     };
107     for (final property in properties) {
108       if (property.type.include != null) {
109         includes.add(property.type.include);
110       }
111       includes.add(
112           'core/field_types/' + property.type.snakeRuntimeCoreName + '.hpp');
113     }
114
115     var sortedIncludes = includes.toList()..sort();
116     for (final include in sortedIncludes) {
117       code.write('#include ');
118       if (include[0] == '<') {
119         code.write(include);
120       } else {
121         code.write('\"$include\"');
122       }
123       code.write('\n');
124     }
125
126     code.writeln('namespace rive {');
127     var superTypeName = defineContextExtension ? 'Core' : _extensionOf?._name;
128     code.writeln('class ${_name}Base : public $superTypeName {');
129
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');
134
135     code.write(comment(
136         'Helper to quickly determine if a core object extends another '
137         'without RTTI at runtime.',
138         indent: 1));
139     code.writeln('bool isTypeOf(uint16_t typeKey) const override {');
140
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:');
145     }
146     code.writeln('return true;');
147     code.writeln('default: return false;}');
148
149     code.writeln('}\n');
150
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};');
156       }
157       code.writeln('private:');
158
159       // Write fields.
160       for (final property in properties) {
161         code.writeln('${property.type.cppName} m_${property.capitalizedName}');
162
163         var initialize = property.initialValueRuntime ??
164             property.initialValue ??
165             property.type.defaultValue;
166         if (initialize != null) {
167           code.write(' = ${property.type.convertCpp(initialize)}');
168         }
169         code.write(';');
170       }
171
172       // Write getter/setters.
173       code.writeln('public:');
174       for (final property in properties) {
175         code.writeln(
176             'inline ${property.type.cppGetterName} ${property.name}() const ' +
177                 (property.isGetOverride ? 'override' : '') +
178                 '{ return m_${property.capitalizedName}; }');
179
180         code.writeln('void ${property.name}(${property.type.cppName} value) ' +
181             (property.isSetOverride ? 'override' : '') +
182             '{'
183                 'if(m_${property.capitalizedName} == value)'
184                 '{return;}'
185                 'm_${property.capitalizedName} = value;'
186                 '${property.name}Changed();'
187                 '}');
188
189         code.writeln();
190       }
191     }
192
193     if (properties.isNotEmpty || _extensionOf == null) {
194       code.writeln('bool deserialize(uint16_t propertyKey, '
195           'BinaryReader& reader) override {');
196
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;');
203       }
204       code.writeln('}');
205       if (_extensionOf != null) {
206         code.writeln('return ${_extensionOf.name}::'
207             'deserialize(propertyKey, reader); }');
208       } else {
209         code.writeln('return false; }');
210       }
211     }
212
213     code.writeln('protected:');
214     if (properties.isNotEmpty) {
215       for (final property in properties) {
216         code.writeln('virtual void ${property.name}Changed() {}');
217       }
218     }
219     code.writeln('};');
220     code.writeln('}');
221
222     var file = File('$generatedHppPath$localCodeFilename');
223     file.createSync(recursive: true);
224
225     var formattedCode =
226         await _formatter.formatAndGuard('${_name}Base', code.toString());
227     file.writeAsStringSync(formattedCode, flush: true);
228
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 {
238         public:
239       };''');
240       concreteCode.writeln('}');
241
242       var formattedCode =
243           await _formatter.formatAndGuard(_name, concreteCode.toString());
244       concreteFile.writeAsStringSync(formattedCode, flush: true);
245     }
246   }
247
248   @override
249   String toString() {
250     return '$_name[${_key?.intValue ?? '-'}]';
251   }
252
253   static const int minPropertyId = 3;
254   static Future<bool> generate() async {
255     // Check dupe ids.
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];
262         if (other != null) {
263           color('Duplicate type ids for $definition and $other.',
264               front: Styles.RED);
265           runGenerator = false;
266         } else {
267           ids[definition._key.intValue] = definition;
268         }
269       }
270       for (final property in definition._properties) {
271         if (property.key.isMissing) {
272           continue;
273         }
274         var other = properties[property.key.intValue];
275         if (other != null) {
276           color(
277               '''Duplicate field ids for ${property.definition}.$property '''
278               '''and ${other.definition}.$other.''',
279               front: Styles.RED);
280           runGenerator = false;
281         } else if (property.key.intValue < minPropertyId) {
282           color(
283               '${property.definition}.$property: ids less than '
284               '$minPropertyId are reserved.',
285               front: Styles.RED);
286           runGenerator = false;
287         } else {
288           properties[property.key.intValue] = property;
289         }
290       }
291     }
292
293     // Find max id, we use this to assign to types that don't have ids yet.
294     int nextFieldId = minPropertyId - 1;
295     int nextId = 0;
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;
301       }
302       for (final field in definition._properties) {
303         if (field != null &&
304             field.key.intValue != null &&
305             field.key.intValue > nextFieldId) {
306           nextFieldId = field.key.intValue;
307         }
308       }
309     }
310
311     if (!runGenerator) {
312       color('Not running generator due to previous errors.',
313           front: Styles.YELLOW);
314       return false;
315     }
316
317     definitions.removeWhere((key, definition) => definition._editorOnly);
318
319     // Clear out previous generated code.
320     var dir = Directory(generatedHppPath);
321     if (dir.existsSync()) {
322       dir.deleteSync(recursive: true);
323     }
324     dir.createSync(recursive: true);
325     // Generate core context.
326
327     for (final definition in definitions.values) {
328       await definition.generateCode();
329     }
330
331     StringBuffer ctxCode = StringBuffer('');
332     var includes = <String>{};
333     for (final definition in definitions.values) {
334       includes.add(definition.concreteCodeFilename);
335     }
336     var includeList = includes.toList()..sort();
337     for (final include in includeList) {
338       ctxCode.writeln('#include "$include"');
339     }
340     ctxCode.writeln('namespace rive {class CoreRegistry {'
341         'public:');
342     ctxCode.writeln('static Core* makeCoreInstance(int typeKey) {'
343         'switch(typeKey) {');
344     for (final definition in definitions.values) {
345       if (definition._isAbstract) {
346         continue;
347       }
348       ctxCode.writeln('case ${definition.name}Base::typeKey:');
349       ctxCode.writeln('return new ${definition.name}();');
350     }
351     ctxCode.writeln('} return nullptr; }');
352
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);
358       }
359     }
360     for (final fieldType in usedFieldTypes.keys) {
361       ctxCode
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;');
372       }
373       ctxCode.writeln('}}');
374     }
375     for (final fieldType in usedFieldTypes.keys) {
376       ctxCode.writeln(
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}();');
386       }
387       ctxCode.writeln('}');
388       ctxCode.writeln('return ${fieldType.defaultValue ?? 'nullptr'};');
389       ctxCode.writeln('}');
390     }
391
392     ctxCode.writeln('static int propertyFieldId(int propertyKey) {');
393     ctxCode.writeln('switch(propertyKey) {');
394
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:');
400       }
401       ctxCode.writeln('return Core${fieldType.capitalizedName}Type::id;');
402     }
403
404     ctxCode.writeln('default: return -1;}}');
405     /*Core makeCoreInstance(int typeKey) {
406     switch (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);
416     //   }
417     // }
418     // // Find fields we use.
419
420     // for (final fieldType in usedFieldTypes) {
421     //   ctxCode.writeln('static ${fieldType.runtimeCoreType} '
422     //       '${fieldType.uncapitalizedName}Type;');
423     // }
424     ctxCode.writeln('};}');
425
426     var output = generatedHppPath;
427     var folder =
428         output != null && output.isNotEmpty && output[output.length - 1] == '/'
429             ? output.substring(0, output.length - 1)
430             : output;
431
432     var file = File('$folder/core_registry.hpp');
433     file.createSync(recursive: true);
434
435     var formattedCode =
436         await _formatter.formatAndGuard('CoreRegistry', ctxCode.toString());
437     file.writeAsStringSync(formattedCode, flush: true);
438
439     return true;
440   }
441 }