zaki wrote:IrrlichtLime definitely has less overhead than Irrlicht.NET, and it doesn't use P/Invoke, so performance must also be better
Yes. Lime provides not bad performance comparing to native C++, since its done in the way you described. Personally i think that most time takes rendering which is done internally by Irrlicht, so will do same speed in C++ and C#. When you get 1000 fps in C++ and get 900 fps in C# equivalent its performance loss, but in general you don't need those 1000 fps, and its not so bad to have 900. Also in real app i believe when C++ will give you 40-50 fps, C# will give the same (maybe -1/-2 fps).
More about performance. Lime compiled in Debug mode should give even bigger performance loss than in Release (comparing to Debug/Release of native Irrlicht), because I add a lot of asserts into the code, in every place where it is clear to know what value acceptable, and this must help user to detect some errors. In Release mode, all the asserts omitted, so it runs as fast as possible.
For example, when you create a Video.Coloru (which is video::SColor), in Debug mode additionally 4 comparisons done each time:
- Code: Select all
Coloru(unsigned int r, unsigned int g, unsigned int b, unsigned int a)
LIME_ASSERT(r <= 255);
LIME_ASSERT(g <= 255);
LIME_ASSERT(b <= 255);
LIME_ASSERT(a <= 255);
m_NativeValue = new video::SColor(a, r, g, b);
Also, next one will help user don't write "new Colorf(255, 255, 255, 255)", since it will give an "assertion failed" in Debug mode:
- Code: Select all
Colorf(float r, float g, float b, float a)
LIME_ASSERT(r >= 0.0f && r <= 1.0f);
LIME_ASSERT(g >= 0.0f && g <= 1.0f);
LIME_ASSERT(b >= 0.0f && b <= 1.0f);
LIME_ASSERT(a >= 0.0f && a <= 1.0f);
m_NativeValue = new video::SColorf(r, g, b, a);
More about performance. One of the ways to improve performance is to implement native value types, like Dimension, Vector, Triangle, SColor and so on, inside Lime. Because now, each type only wraps its native value by having a reference on it internally. And when you do anything with it, its done with that reference, so by native Irrlicht. This have advantages and disadvantages:
"plus" you don't copy functionality (bodies of methods) from native Irrlicht;
"plus" if native implementation changes (or bug fixed) you don't need to change anything;
"plus" if some Irrlicht' method takes a reference on that value internally and changes it, you don't need to bother "how to sync value with my managed copy?".
"minus" performance loss, for example next one is definitely a bit slower:
- Code: Select all
bool GetIntersectionWithSphere(Vector3Df^ sphereOrigin, float sphereRadius, [Out] double% distance)
LIME_ASSERT(sphereOrigin != nullptr);
f64 d = 0.0f;
bool b = m_NativeValue->getIntersectionWithSphere(
distance = d;
but thats not a big price, and it is not a 100% truth that it is possible to implement this method inside Lime and it will be not slower than its native analog.
zaki wrote:For curiousity, greenya, how do you work with IReferenceCounted? I'm not very familiar with managed C++, is there any place we need to be cautious about GC or is it something that will just work with C++?
Each interface (or almost each) has its own wrapping managed class. And IReferenceCounted is not an exception. Lime doesn't do any "automatic" reference counting that you might think. If native Irrlicht doesn't do it, Lime doesn't do it as well. For example if user does bad reference counting, like doing double Drop(), Lime falls like native Irrlicht does. In C++ you do not use "new" and "delete" for Irrlicht, you use "create" and "drop", so Lime wraps this and doesn't uses IDisposable managed interface for this things, because i don't think its right way to help user in this -- better user must know what he doing.
Also, for example Irrlicht.NETCP provides a constructor for IrrlichtDevice, so you create the device by creating an object using "new". I don't think its good way, since than you must enclose executing into try-catch, because if device failed to create, Irrlicht doesn't fall and just return null pointer, which is indication of error. In Lime it's just the same, we have IrrlichtLime.CreateDevice() (a static method), it may return null and it is only way to create the device (just like native Irrlicht do this).
Wrapping in this way, gives us a bonus in further supporting, becasue if native Irrlicht changes internally we just wrap this in that simple way, not building our different engine on Irrlicht, so when base changes some specific part strong enough, we changed only that part and all other part should work just the same if they wasn't changed in native Irrlicht.
zaki wrote:The page I found on the mono site recommends compiling with the /clr:safe switch, but I don't think that is possible, because IrrlichtLime links against native Irrlicht.
Yes, if you use "/clr:safe" than it just managed code and i guess it can run OK. But this is not our case, since "safe" means "no unmanaged pointers at all" which is impossible. If i turn "safe" on, i get million of
error C4956: 'char *' : this type is not verifiable
If i use "/clr:pure", i don't get messages about native pointers, but i get a million of
error C3862: 'swprintf': cannot compile an unmanaged function with /clr:pure or /clr:safe
so i can compile it only as "unsafe".