Monthly Archives: May 2013

Mombari won Third Place of the Appcampus Competition

 
Great news: Mombari won the third place of the Appcampus Germany Competition.

You still don’t know what Mombari is?

Mombari

Mombari in Action…

Mombari is an upcoming Windows Phone 8 game. Click here for more information: Mombari

Many thanks to the Jury. That’s awesome – I’m absolutely delighted!

Here is the link to the Microsoft MSDN press release (German).

Access to Mono’s Low Level Profiling Functions in Unity

Unity has already quite useful profiling functions, but certain information are still difficult to gather. I once had to determine the cause of a memory leak problem in a Unity project not written by me. That was kind of hard to find. Should I encounter such a problem again it would be nice to have a tool-based approach.

Inspired by a recent twitter discussion it occurred to me that one could possibly hook into the low level profile interface of Mono.

One can take a look at Mono’s profiler header here:
profiler.h

The code (and documentation^^) for the profiler can be found here:
profiler.c

Probably the only way to determine, if that’s feasible at all, is to quickly create a minimal plugin.

So can we call these methods in the Mono runtime of Unity?

Here’s a quick and dirty attempt:

#define WIN32_LEAN_AND_MEAN
#define EXPORT_API __declspec(dllexport)

#include <stdio.h>
#include <Windows.h>

//
//  MonoProfilePlugin.c
//
//  Created by stephan / Software 7 GmbH on 21.05.13 - 17:14
//  Only a quick & dirty hack
//  use at your own risk
//

static HMODULE monoLibraryHandle;

//did I already mention that it's a quick and dirty hack? 
struct _MonoMethod {
    short flags;  
    short iflags; 
    int token;
    char *klassDummy;
    char *signatureDummy;
    const char *name;
	//some fields were omitted
};

typedef struct _MonoMethod MonoMethod;
        
typedef enum {
    MONO_PROFILE_NONE = 0,
    MONO_PROFILE_APPDOMAIN_EVENTS = 1 << 0,
    MONO_PROFILE_ASSEMBLY_EVENTS  = 1 << 1,
    MONO_PROFILE_MODULE_EVENTS    = 1 << 2,
    MONO_PROFILE_CLASS_EVENTS     = 1 << 3,
    MONO_PROFILE_JIT_COMPILATION  = 1 << 4,
    MONO_PROFILE_INLINING         = 1 << 5,
    MONO_PROFILE_EXCEPTIONS       = 1 << 6,
    MONO_PROFILE_ALLOCATIONS      = 1 << 7,
    MONO_PROFILE_GC               = 1 << 8,
    MONO_PROFILE_THREADS          = 1 << 9,
    MONO_PROFILE_REMOTING         = 1 << 10,
    MONO_PROFILE_TRANSITIONS      = 1 << 11,
    MONO_PROFILE_ENTER_LEAVE      = 1 << 12,
    MONO_PROFILE_COVERAGE         = 1 << 13,
    MONO_PROFILE_INS_COVERAGE     = 1 << 14,
    MONO_PROFILE_STATISTICAL      = 1 << 15,
    MONO_PROFILE_METHOD_EVENTS    = 1 << 16,
    MONO_PROFILE_MONITOR_EVENTS   = 1 << 17,
    MONO_PROFILE_IOMAP_EVENTS     = 1 << 18, 
    MONO_PROFILE_GC_MOVES         = 1 << 19,
    MONO_PROFILE_GC_ROOTS         = 1 << 20
} MonoProfileFlags;


typedef enum
{
    MONO_GC_EVENT_START,
    MONO_GC_EVENT_MARK_START,
    MONO_GC_EVENT_MARK_END,
    MONO_GC_EVENT_RECLAIM_START,
    MONO_GC_EVENT_RECLAIM_END,
    MONO_GC_EVENT_END,
    MONO_GC_EVENT_PRE_STOP_WORLD,
    MONO_GC_EVENT_POST_STOP_WORLD,
    MONO_GC_EVENT_PRE_START_WORLD,
    MONO_GC_EVENT_POST_START_WORLD
} MonoGCEvent;

struct _MonoProfiler {
    char dummy[10];
};
        
typedef struct _MonoProfiler MonoProfiler;

static FILE *fp;
static MonoProfiler _monoProfiler;
static BOOLEAN gc_eventCalledAtLeastOnce = FALSE;
static BOOLEAN gc_resizeCalledAtLeastOnce = FALSE;
static BOOLEAN sample_method_enterCalledAtLeastOnce = FALSE;
static BOOLEAN sample_method_leaveCalledAtLeastOnce = FALSE;

typedef void (*MonoProfileFunc) (MonoProfiler *prof);
typedef void (*MonoProfileMethodFunc) (MonoProfiler *prof, MonoMethod* method);
typedef void (*MonoProfileGCFunc) (MonoProfiler *prof, MonoGCEvent event, int generation);
typedef void (*MonoProfileGCResizeFunc)   (MonoProfiler *prof, __int64 new_size);

typedef void (*mono_profiler_installFunc) (MonoProfiler *prof, MonoProfileFunc shutdown_callback);
typedef void (*mono_profiler_install_enter_leaveFunc) (MonoProfileMethodFunc enterCallback, MonoProfileMethodFunc leaveCallback);
typedef void (*mono_profiler_set_eventsFunc) (MonoProfileFlags events);
typedef void (*mono_profiler_install_gcFunc) (MonoProfileGCFunc callback, MonoProfileGCResizeFunc heap_resize_callback);

static mono_profiler_installFunc mono_profiler_install;
static mono_profiler_install_enter_leaveFunc mono_profiler_install_enter_leave;
static mono_profiler_set_eventsFunc mono_profiler_set_events;
static mono_profiler_install_gcFunc mono_profiler_install_gc;
    
static void debug_out(const char *txt)
{
    if(fp != NULL) {
        fprintf(fp, "%s\n", txt);
        fflush(fp);
    }
}

static void profiler_shutdown (MonoProfiler *prof)
{
    debug_out("profiler_shutdown");        
}

static void gc_event(MonoProfiler *profiler, MonoGCEvent event, int generation)
{
	if(!gc_eventCalledAtLeastOnce) {
		debug_out("gc_event");
		gc_eventCalledAtLeastOnce = TRUE;
	}

}

static void gc_resize (MonoProfiler *profiler, __int64 new_size)
{
	if(!gc_resizeCalledAtLeastOnce) {
		debug_out("gc_resize");
		gc_resizeCalledAtLeastOnce = TRUE;
	}
}

static void sample_method_enter (MonoProfiler *prof, MonoMethod *method)
{
	if(!sample_method_enterCalledAtLeastOnce) {
		debug_out("sample_method_enter");
		debug_out(method->name);
		sample_method_enterCalledAtLeastOnce = TRUE;
	}
}
        
static void sample_method_leave (MonoProfiler *prof, MonoMethod *method)
{
	if(!sample_method_leaveCalledAtLeastOnce) {
		debug_out("sample_method_leave");
		debug_out(method->name);
		sample_method_leaveCalledAtLeastOnce = TRUE;
	}        
}

void EXPORT_API InitializeProfiler()
{
    fopen_s(&fp, "C:\\Users\\stephan\\tmp\\mytestlog.txt", "w");
        
    debug_out("InitializeProfiler");
        
	monoLibraryHandle = LoadLibrary(L"C:\\Program Files (x86)\\Unity\\Editor\\Data\\Mono\\EmbedRuntime\\mono.dll");
	if(monoLibraryHandle) { 
		mono_profiler_install = (mono_profiler_installFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install");
		mono_profiler_install_gc = (mono_profiler_install_gcFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install_gc");
		mono_profiler_install_enter_leave = (mono_profiler_install_enter_leaveFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install_enter_leave");
		mono_profiler_set_events = (mono_profiler_set_eventsFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_set_events");
		debug_out("after GetProcAddress");

	    mono_profiler_install (&_monoProfiler, profiler_shutdown);
		debug_out("after mono_profiler_install");

		mono_profiler_install_gc (gc_event, gc_resize);
		debug_out("after mono_profiler_install_gc");
        
		mono_profiler_install_enter_leave (sample_method_enter, sample_method_leave);
		debug_out("after mono_profiler_install_enter_leave");

		mono_profiler_set_events((MonoProfileFlags)(MONO_PROFILE_ENTER_LEAVE | MONO_PROFILE_GC));
		debug_out("after mono_profiler_set_events");
	}
	else {
		debug_out("Loading mono.dll failed!");			
	}
}

Calling the plugin from Unity:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class MyTest : MonoBehaviour
{
	[DllImport ("MonoProfilePlugin")]
	private static extern void InitializeProfiler();

	void Start ()
	{
		InitializeProfiler();
		//we need to call a method to see whether sample_method_enter/-leave works
		Debug.Log(AddIntegers(1, 2));
	}

	void Update ()
	{
	}

	private int AddIntegers(int a, int b)
	{
		return a + b;
	}
}

Now it gets interesting.

What will we find in mytestlog.txt?

This is the result:

InitializeProfiler
after GetProcAddress
after mono_profiler_install
after mono_profiler_install_gc
after mono_profiler_install_enter_leave
after mono_profiler_set_events
sample_method_enter
AddIntegers
sample_method_leave
AddIntegers
gc_event
profiler_shutdown

Wooh. This means it works. We successfully hooked into Mono’s profiling.

Next step would be to collect all the allocation data together with the corresponding stack traces and add some reporting.

Unfortunately currently I don’t have time for this. Perhaps I can later work on it. Or perhaps someone wants to develop this further? (HINT, HINT, HINT) ­čÖé

Developing with Unity for MonoGame II

 

There were some responses to my last blog post entry.

Among the comments and feedback, there was also the question: why using Unity?

This question was a little surprising to me, because my impression is that Unity is currently the absolute dominant game development environment?!

I personally think that Unity offers enormous advantages:
– Rapid development, fast edit / test cycles
– Allows easy customizations of the editor by means of simple scripts
– The asset store

BTW: What are your favorite features in Unity?

I made a 3 minute screencast showing a typical turnaround cycle:
– Construction of a new level in Unity
– Add the level to the FFWD configuration
– Conversion of the project to XNA
– Compilation of the assets (Content Pipeline) in VS2010 to .xnb files
– Transfer of .xnb files to a Windows Phone 8 project
– Deploy and test the game

Of course this can only be a quick overview of the workflow. I will write more posts about it.

Are there certain things that you want to see in the next blog post?

If not, I’m going with things that I think are interesting about this workflow.

So just let me know what you are interested in.