convert this: peculiarities of cross-platform mobile game development at vizor
TRANSCRIPT
Who am I ?
Previously at:«Unity3D глазами программиста графики»DevGamm! Минск, осень 2014.
Роман Чеховский• Архитектор мобильного движка Vizor Games.• 4+ лет опыта работы с графикой, мобильными играми, и движками.• Работал с Unity3D, UE, Cocos2D, libgdx, Urho3D, Ogre3D, Adobe flash, Irrlicht, проприетарными технологиями.
• Развивался в процессе разработки проекта.• Кроссплатформенный: Windows, OS X, Linux – JDK.
Android 4.0+, iOS – Native (conversion).
• Первый релиз – “Зомби Инфо” – Апрель 2013.• Множество структурных и функциональных
оптимизаций для мобильных устройств.• Data-driven насколько это возможно.• Смычка с API устройств через бриджи на С+
+.• 95% кода написано на Java.
Cобственный мобильный движок
–• Много runtime-проверок.• Отсутствие значимых
типов,• Отсутствие делегатов.• Отсутствие обобщенных
типов во время выполнения.
• Издержки на сборку мусора.
• Нет среды выполнения на iOS.
Java как язык разработки+
• Типобезопасность.• Отсутствие pointer
arithmetic.• Большая стандартная
библиотека.• Средства
выразительности.• Автоматическая сборка
мусора.• Приемлемая
производительность.jMonkeyEngine1 http://jmonkeyengine.org - jMonkeyEngine official site2 https://libgdx.badlogicgames.com - ligGDX official site
Коллекции
! Error(x, y): java: generic array creation
1. Не могут содержать примитивы
решение: Не изобретать своих коллекций для ссылочных типов.
решение: IntArrayList, FloatArrayList, IntIntHashMap, etc, не реализующие iterator().
3. Присутствует type erasure и издержки на приведение типов в run-timeрешение: Насколько это возможно, ускорить cast и instanceof на
runtime-уровне.
2. Нельзя инстанциировать generic arraypublic class ArrayList0<T> extends ...{ private T[] objects;
public ArrayList0(int capacity) { objects = new T[capacity]; }
// и так далее}
public class ArrayList1<T> extends ...{ private T[] elements;
public ArrayList1(Class<T> klass, int capacity) { elements = java.lang.reflect.Array.newInstance( klass, capacity ); }
// и так далее}
Значимые типы1. Их нет :(
List<Vertex3D> vs = new ArrayList<>();
for (int i = 0; i < ...; ++i) vs.add(new Vertex3D());
context.setStreamSource(vs);
• Колоссальные расходы на инстанциирование
• Footprint объектов
2. …но с этим можно жить :)public class Vertex3DModel extends VertexModel { VertexElem position = VertexElem.POSITION3F; VertexElem rotation = VertexElem.ROTATION4F; VertexElem scale = VertexElem.SCALE3F;}
• Невыровненная память
Vertex3DModel m = new Vertex3DModel();VertexArray vs = new VertexArray(m);
for (int i = 0; i < ...; ++i) vs.add(...);
context.setStreamSource(vs);
• Данные хранятся в виде float[]• Инты кладем используя floatToRawIntBits()
• Байты упаковыаем в int’ы• Значительный overhead для GC
Проблемы: Факты:
• Double-precision пока без необходим
public class Vertex3D extends Vertex{ Vector3 position = new Vector3(); Quaternion rotation = new Quaternion(); Vector3 scale = new Vector3();}
Performance Apple iOS
Java - проблемы
JNI-бриджинг AOT-компиляцияКонверсия кода
J2ObjC RoboVM
3 https://github.com/google/j2objc - j2ObjC github repo4 https://github.com/robovm/robovm - RoboVM github repo
j2cpp2 Java DesktopNative plug-ins
Конвертер - требования
•Конверсия исходного кода на java в С++ 11.•Совместимость с java 1.8 (lambdas, method references).•Высокая скорость конверсии на многоядерном железе.•Читаемость и отлаживаемость сгенерированного кода.
Конвертер: AST*.jav
aCompilation
unitConcurrencypackage com.vizor.data;
class A<T> extends ... implements ... { private T[] field;
public A(T[] data) {
field = data; }
/** Выводит на экран i’й аргумент * @param i индекс */ public void foo(int arg) { System.out.println(field[arg]); }}
5
Lexer
Parser+
5 https://github.com/javaparser/javaparser - javaparser library github repo
Class Declaration
MethodsExtension
Class Name
Class NameMethod Declaration
Method Name CommentMethod Body
Assignment Invocation
ValueTarget Target Methodname
Конвертер: CST
Compilation
unitFP
VisitorSP
VisitorTP
VisitorCST
project
Concurrency
Write Atomicity
@Overrideprotected int foo() { String msg = “Hello” + “world”; int n = 0;
for (int i = 0; i < msg.length(); ++i) { if (msg.charAt(i) == ‘o’) n++; }
return n;}
Method: com.vizor.B.foo():int overrides com.vizor.A.foo():int
Class: java.lang.String
Method: java.lang.String.length():int
Method: java.lang.String.charAt(int):char
Method: java.lang.String.append(S):S
В AST указания типа - строки
Конвертер: Visitors 1. FP Visitor – Объявления классов, интерфейсов, перечислений.2. SP Visitor – Конструкторы, поля, методы, константы
перечислений.3. TP Visitor – Циклы, блоки кода, операторы, вызовы методов и
приведения типов.
• СST Project – база знаний о связях в AST.• Информация о типах собирается в bulk type cache.• Помечаются ссылки на использованные типы,
переопределенные методы, etc.• Помечаются ссылки на вызываемые методы и использованные
поля.• На следующем этапе код обрабатывается процессорами.
Конвертер: Процессоры 1.Var Args remover2.Generics remover3.Anonymous class remover4.Inner class remover5.Nested class remover6.For Each remover7.Auto boxing remover8.Enum remover9.Conflicting names renamer10.Method return type fixer11.Method redeclaration
fixer12.String finalizer13.Explicit constructor call
adder
class ShapeBuilder { public Shape build() { ....}
class CircleBuilder extends ShapeBuilder { @Override public Circle build() { ....}
CircleBuilder cb = new CircleBuilder();Circle c = cb.build();
Method redeclaration fixer:
class CircleBuilder extends ShapeBuilder { @Override public Shape build() { ....}
CircleBuilder cb = new CircleBuilder();Circle c = (Circle)cb.build();
Конвертер: Печать CST
project
HPP Printer
CPP Printer
method prototypesclass declarations
class definitionusing types
fields
method bodies#include files
statics init
A.hpp A.cpp
HPP Printer
CPP Printer
B.hpp B.cpp
HPP Printer
CPP Printer
C.hpp C.cpp
A B C … Z
A (class) B (class) C (class)
Thread Pool
Конвертер: Showcasepublic static void unique(List<? extends Item> list){ Iterator<? extends Item> it = list.iterator(); int count = 0;
while (it.hasNext()) { Item object = it.next();
for (int i = 0; i < count; i++) { Item o = list.get(i); if (o.id.equals(object.id)) { it.remove(); count--; break; } }
count++; }}
jvmtypes::jvoid Item::unique(List* list){ Iterator* it = list->iterator(); jvmtypes::jint count = 0;
while (it->hasNext()) { Item* object = (jCAST<Item>(it->next()));
for (jvmtypes::jint i = 0; i < count; i++) { Item* o = (jCAST<Item>(list->get(i))); if (o->id->equals(object->id)) { it->remove(); count--; break; } }
count++; }}
List<GameObject> l = new ArrayList<>(42);// ...GameObject o = l.get(0);
List l = new ArrayList(42);// ...GameObject o = (GameObject) l.get(0);
NEW java/util/ArrayListDUPINVOKESPECIAL java/util/ArrayList.<init> ()VASTORE 1ALOAD 1ICONST_0INVOKEINTERFACE java/util/List.get (I)Ljava/lang/ObjectCHECKCAST com/vizor/mobile/engine/GameObjectASTORE 2
List* vectors = ArrayList::$alloc()->$ctor_ArrayList(42);// ...GameObject* v = static_cast<GameObject*>(vectors->get(0));
Type erasure
Такого простого приведения типа очевидно недостаточно. Почему?
public class GameObject extends ? implements ?
class GameObject : public Entity, public Serializable, public virtual Object
java.lang.Object
com.vizor.Entityjava.lang.Serializable
com.vizor.GameObject<<implements>> <<extends>>
virtual inheritancevirtual inheritance
Virtual inheritance
static_cast - статическое преобразование c проверкой в compile time.
! Cannot cast ‘Base *' to ‘Derived *' via virtual base ‘Base'
reinterpret_cast - смена представления указателя или ссылки без каких бы то ни было проверок или гарантий ни в compile time, ни в runtime.
Type traits
dynamic_cast – динамическое преобразование указателя или ссылки с проверкой в runtime.
Медленный, требует опцию -frtti
Object* o = ->ctor_GameObject(...);
ADDRESSADDRESS + THUNK
GameObject::$alloc()
“INVOKESPECIAL”
Таким образом суть каста сводится к убиранию этого adjusment’a c с проверкой достижимости.
java.lang.Object
java.lang.Serializable
com.vizor.Entity
com.vizor.GameObject
6 http://www.programering.com/a/MDOxgTNwATM.html - memory layout for dynamic objects
Object* o = new GameObject();GameObject* go = dynamic_cast<GameObject>(o);
std::cout << (uintptr_t)o; // 0x10000010std::cout << (uintptr_t)go; // 0x10000000
o != go
jCASTGameObject* o = jCAST<GameObject>(o); -> return (GameObject*)o->$cast_to(GameObject::CLASS_ID);
void* $getPointer(){ return this; }
void* $castTo(const int to){ switch (to) { case GameObject::CLASS_ID: return GameObject::$getPointer() case Serializable::CLASS_ID: return Serializable::$getPointer(); case Entity::CLASS_ID: return Entity::$getPointer(); case Object::CLASS_ID: return Object::$getPointer(); default: return 0; }}
7 http://www.drdobbs.com/cpp/multiple-inheritance-considered-useful - dynamic_cast trough vtable
0
10000000
20000000
30000000
40000000
50000000
60000000
dynamic_cast jCAST
5-10X speed-up on real hardware
1. Java, на самом деле, подходит как язык для написания движка, с принятием во внимание её особенностей.
Выводы:
2. Любая проблема может быть достойно решена на архитектурном уровне.
3. Индустрия показывает, что конверсия кода – перспективное решение, к которому разработчики прибегают всё чаще.
Спасибо за внимание!Вопросы…?