On Tuesday, 20 March 2018 at 21:27:53 UTC, 12345swordy wrote:
This is very important to me as I am very interested in using the language for game development.

Yes I know that it's marked as "Duplicated", but I strongly disagree as it is different enough to consider is own issue.

Alex

https://issues.dlang.org/show_bug.cgi?id=17592

Yeah that's pretty poopy. I looked into this and I think this is fixable. Here's my proof of concept:

-----------------
import core.stdc.stdio;
import std.traits;

/*******************************************************************
* This code was copied right out of druntime and
* attributed with @nogc
*******************************************************************/
extern (C) void rt_finalize(void *data, bool det=true) @nogc;
void destroy(T)(T obj) @nogc if (is(T == class))
{
    rt_finalize(cast(void*)obj);
}

/*******************************************************************
* This emplace implementation below was copied
* right out of std.conv and attributed with @nogc
*******************************************************************/
@nogc pure nothrow @safe
void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName)
{
assert(chunk.length >= typeSize, "emplace: Chunk size too small."); assert((cast(size_t) chunk.ptr) % typeAlignment == 0, "emplace: Chunk is not aligned.");
}

T emplace(T, Args...)(T chunk, auto ref Args args) @nogc
        if (is(T == class))
{
    static assert(!isAbstractClass!T, T.stringof ~
        " is abstract and it can't be emplaced");

    // Initialize the object in its pre-ctor state
    enum classSize = __traits(classInstanceSize, T);
(() @trusted => (cast(void*) chunk)[0 .. classSize] = typeid(T).initializer[])();

    static if (isInnerClass!T)
    {
        static assert(Args.length > 0,
"Initializing an inner class requires a pointer to the outer class");
        static assert(is(Args[0] : typeof(T.outer)),
"The first argument must be a pointer to the outer class");

        chunk.outer = args[0];
        alias args1 = args[1..$];
    }
    else alias args1 = args;

    // Call the ctor if any
    static if (is(typeof(chunk.__ctor(args1))))
    {
        // T defines a genuine constructor accepting args
        // Go the classic route: write .init first, then call ctor
        chunk.__ctor(args1);
    }
    else
    {
        static assert(args1.length == 0 && !is(typeof(&T.__ctor)),
            "Don't know how to initialize an object of type "
~ T.stringof ~ " with arguments " ~ typeof(args1).stringof);
    }
    return chunk;
}

T emplace(T, Args...)(void[] chunk, auto ref Args args) @nogc
        if (is(T == class))
{
    enum classSize = __traits(classInstanceSize, T);
testEmplaceChunk(chunk, classSize, classInstanceAlignment!T, T.stringof);
    return emplace!T(cast(T)(chunk.ptr), args);
}

/*******************************************************************
* This code was copied from https://wiki.dlang.org/Memory_Management#Explicit_Class_Instance_Allocation
* and attributed with @nogc
*******************************************************************/
class TestClass
{
    int x;

    this(int x) @nogc
    {
        puts("TestClass's constructor called");
        this.x = x;
    }

    ~this() @nogc
    {
        puts("TestClass's destructor called");
    }
}

T heapAllocate(T, Args...) (Args args) @nogc
{
    import core.stdc.stdlib : malloc;
    import core.memory : GC;

    // get class size of class instance in bytes
    auto size = __traits(classInstanceSize, T);

    // allocate memory for the object
    auto memory = malloc(size)[0..size];
    if(!memory)
    {
        import core.exception : onOutOfMemoryError;
        onOutOfMemoryError();
    }

    puts("Memory allocated");

    // notify garbage collector that it should scan this memory
    GC.addRange(memory.ptr, size);

    // call T's constructor and emplace instance on
    // newly allocated memory
    return emplace!(T, Args)(memory, args);
}

void heapDeallocate(T)(T obj) @nogc
{
    import core.stdc.stdlib : free;
    import core.memory : GC;

    // calls obj's destructor
    destroy(obj);

    // garbage collector should no longer scan this memory
    GC.removeRange(cast(void*)obj);

    // free memory occupied by object
    free(cast(void*)obj);

    puts("Memory deallocated");
}

void main() @nogc
{
    // allocate new instance of TestClass on the heap
    auto test = heapAllocate!TestClass(42);
    scope(exit)
    {
        heapDeallocate(test);
    }

    printf("test.x = %d\n", test.x);
}
-------------

Step 1.  Make `emplace` @nogc
So we need to attribute `std.conv.emplace` as @nogc. Based on the code above, that looks feasible. The difficulty, though will be writing thorough tests for it.

Step 2. Make `destroy` @nogc
`destroy` simply calls `rt_finalize` in the runtime, at least for classes. I declared it as `@nogc` in the code above, but that's a cheat, though I think `rt_finalize` can be made `@nogc` in the runtime.

I'll try to do step 2. If someone wants to help with step 1, that would be great.

Mike

Reply via email to