[Adaptation Layer] Added rive-tizen adaptation layer class.
[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 localCppCodeFilename => '${stripExtension(_filename)}_base.cpp';
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 (!_isAbstract) {
194       code.writeln('Core* clone() const override;');
195     }
196
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};');
202       }
203       if (_extensionOf != null) {
204         code.writeln('${_extensionOf.name}::'
205             'copy(object); ');
206       }
207       code.writeln('}');
208       code.writeln();
209
210       code.writeln('bool deserialize(uint16_t propertyKey, '
211           'BinaryReader& reader) override {');
212
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;');
219       }
220       code.writeln('}');
221       if (_extensionOf != null) {
222         code.writeln('return ${_extensionOf.name}::'
223             'deserialize(propertyKey, reader); }');
224       } else {
225         code.writeln('return false; }');
226       }
227     }
228
229     code.writeln('protected:');
230     if (properties.isNotEmpty) {
231       for (final property in properties) {
232         code.writeln('virtual void ${property.name}Changed() {}');
233       }
234     }
235     code.writeln('};');
236     code.writeln('}');
237
238     var file = File('$generatedHppPath$localCodeFilename');
239     file.createSync(recursive: true);
240
241     var formattedCode =
242         await _formatter.formatAndGuard('${_name}Base', code.toString());
243     file.writeAsStringSync(formattedCode, flush: true);
244
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 {
254         public:
255       };''');
256       concreteCode.writeln('}');
257
258       var formattedCode =
259           await _formatter.formatAndGuard(_name, concreteCode.toString());
260       concreteFile.writeAsStringSync(formattedCode, flush: true);
261     }
262     if (!_isAbstract) {
263       StringBuffer cppCode = StringBuffer();
264       cppCode.writeln('#include "generated/$localCodeFilename"');
265       cppCode.writeln('#include "$concreteCodeFilename"');
266       cppCode.writeln();
267       cppCode.writeln('using namespace rive;');
268       cppCode.writeln();
269       cppCode.writeln('Core* ${_name}Base::clone() const { '
270           'auto cloned = new $_name(); '
271           'cloned->copy(*this); '
272           'return cloned; '
273           '}');
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);
278     }
279   }
280
281   @override
282   String toString() {
283     return '$_name[${_key?.intValue ?? '-'}]';
284   }
285
286   static const int minPropertyId = 3;
287   static Future<bool> generate() async {
288     // Check dupe ids.
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];
295         if (other != null) {
296           color('Duplicate type ids for $definition and $other.',
297               front: Styles.RED);
298           runGenerator = false;
299         } else {
300           ids[definition._key.intValue] = definition;
301         }
302       }
303       for (final property in definition._properties) {
304         if (property.key.isMissing) {
305           continue;
306         }
307         var other = properties[property.key.intValue];
308         if (other != null) {
309           color(
310               '''Duplicate field ids for ${property.definition}.$property '''
311               '''and ${other.definition}.$other.''',
312               front: Styles.RED);
313           runGenerator = false;
314         } else if (property.key.intValue < minPropertyId) {
315           color(
316               '${property.definition}.$property: ids less than '
317               '$minPropertyId are reserved.',
318               front: Styles.RED);
319           runGenerator = false;
320         } else {
321           properties[property.key.intValue] = property;
322         }
323       }
324     }
325
326     // Find max id, we use this to assign to types that don't have ids yet.
327     int nextFieldId = minPropertyId - 1;
328     int nextId = 0;
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;
334       }
335       for (final field in definition._properties) {
336         if (field != null &&
337             field.key.intValue != null &&
338             field.key.intValue > nextFieldId) {
339           nextFieldId = field.key.intValue;
340         }
341       }
342     }
343
344     if (!runGenerator) {
345       color('Not running generator due to previous errors.',
346           front: Styles.YELLOW);
347       return false;
348     }
349
350     definitions.removeWhere((key, definition) => definition._editorOnly);
351
352     // Clear out previous generated code.
353     var dir = Directory(generatedHppPath);
354     if (dir.existsSync()) {
355       dir.deleteSync(recursive: true);
356     }
357     dir.createSync(recursive: true);
358     // Generate core context.
359
360     for (final definition in definitions.values) {
361       await definition.generateCode();
362     }
363
364     StringBuffer ctxCode = StringBuffer('');
365     var includes = <String>{};
366     for (final definition in definitions.values) {
367       includes.add(definition.concreteCodeFilename);
368     }
369     var includeList = includes.toList()..sort();
370     for (final include in includeList) {
371       ctxCode.writeln('#include "$include"');
372     }
373     ctxCode.writeln('namespace rive {class CoreRegistry {'
374         'public:');
375     ctxCode.writeln('static Core* makeCoreInstance(int typeKey) {'
376         'switch(typeKey) {');
377     for (final definition in definitions.values) {
378       if (definition._isAbstract) {
379         continue;
380       }
381       ctxCode.writeln('case ${definition.name}Base::typeKey:');
382       ctxCode.writeln('return new ${definition.name}();');
383     }
384     ctxCode.writeln('} return nullptr; }');
385
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);
391       }
392     }
393     for (final fieldType in usedFieldTypes.keys) {
394       ctxCode
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;');
405       }
406       ctxCode.writeln('}}');
407     }
408     for (final fieldType in usedFieldTypes.keys) {
409       ctxCode.writeln(
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}();');
419       }
420       ctxCode.writeln('}');
421       ctxCode.writeln('return ${fieldType.defaultValue ?? 'nullptr'};');
422       ctxCode.writeln('}');
423     }
424
425     ctxCode.writeln('static int propertyFieldId(int propertyKey) {');
426     ctxCode.writeln('switch(propertyKey) {');
427
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:');
433       }
434       ctxCode.writeln('return Core${fieldType.capitalizedName}Type::id;');
435     }
436
437     ctxCode.writeln('default: return -1;}}');
438     /*Core makeCoreInstance(int typeKey) {
439     switch (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);
449     //   }
450     // }
451     // // Find fields we use.
452
453     // for (final fieldType in usedFieldTypes) {
454     //   ctxCode.writeln('static ${fieldType.runtimeCoreType} '
455     //       '${fieldType.uncapitalizedName}Type;');
456     // }
457     ctxCode.writeln('};}');
458
459     var output = generatedHppPath;
460     var folder =
461         output != null && output.isNotEmpty && output[output.length - 1] == '/'
462             ? output.substring(0, output.length - 1)
463             : output;
464
465     var file = File('$folder/core_registry.hpp');
466     file.createSync(recursive: true);
467
468     var formattedCode =
469         await _formatter.formatAndGuard('CoreRegistry', ctxCode.toString());
470     file.writeAsStringSync(formattedCode, flush: true);
471
472     return true;
473   }
474 }