diff --git a/source/tanya/memory/allocator.d b/source/tanya/memory/allocator.d index df2c77c..16309e1 100644 --- a/source/tanya/memory/allocator.d +++ b/source/tanya/memory/allocator.d @@ -11,7 +11,6 @@ module tanya.memory.allocator; import std.experimental.allocator; -import std.traits; import std.typecons; /** @@ -178,5 +177,39 @@ unittest assert(p is null); } -enum bool isFinalizable(T) = is(T == class) || is(T == interface) - || hasElaborateDestructor!T || isDynamicArray!T; +/** + * Mixin to get around the impossibility to define a default constructor for + * structs. It can be used for the structs that don't disable the default + * constructor and don't wan't to force passing the allocator each time to + * the constructor. + * + * It defines the private property `allocator`, a constructor that accepts only + * an allocator instance and the method `checkAllocator` that checks if an + * allocator is set and sets it to ` $(D_PSYMBOL theAllocator) if not. + * + * `checkAllocator` should be used at beginning of functions that + * allocate/free memory. + */ +mixin template StructAllocator() +{ + private IAllocator allocator; + + this(IAllocator allocator) + in + { + assert(allocator !is null); + } + body + { + this.allocator = allocator; + } + + pragma(inline, true) + private void checkAllocator() nothrow @safe @nogc + { + if (allocator is null) + { + allocator = theAllocator; + } + } +} diff --git a/source/tanya/memory/package.d b/source/tanya/memory/package.d index 2893849..a524504 100644 --- a/source/tanya/memory/package.d +++ b/source/tanya/memory/package.d @@ -10,5 +10,6 @@ */ module tanya.memory; -public import tanya.memory.allocator; public import std.experimental.allocator; +public import tanya.memory.allocator; +public import tanya.memory.types; diff --git a/source/tanya/memory/types.d b/source/tanya/memory/types.d new file mode 100644 index 0000000..655740d --- /dev/null +++ b/source/tanya/memory/types.d @@ -0,0 +1,401 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.memory.types; + +import core.exception; +import std.algorithm.comparison; +import std.algorithm.mutation; +import std.conv; +import std.traits; +import tanya.memory; +import tanya.traits; + +/** + * Reference-counted object containing a $(D_PARAM T) value as payload. + * $(D_PSYMBOL RefCounted) keeps track of all references of an object, and + * when the reference count goes down to zero, frees the underlying store. + * + * Params: + * T = Type of the reference-counted value. + */ +struct RefCounted(T) +{ + static if (isReference!T) + { + private T payload; + } + else + { + private T* payload; + } + + private uint *counter; + + invariant + { + assert(counter is null || allocator !is null); + } + + /** + * Takes ownership over $(D_PARAM value), setting the counter to 1. + * + * Params: + * value = Value whose ownership is taken over. + * allocator = Allocator used to destroy the $(D_PARAM value) and to + * allocate/deallocate internal storage. + + * Precondition: $(D_INLINECODE allocator !is null) + */ + this(T value, IAllocator allocator = theAllocator) + in + { + assert(allocator !is null); + } + body + { + this.allocator = allocator; + initialize(); + move(value, get); + } + + /// Ditto. + this(IAllocator allocator) + in + { + assert(allocator !is null); + } + body + { + this.allocator = allocator; + } + + /** + * Allocates the internal storage. + */ + private void initialize() + { + static if (isReference!T) + { + counter = allocator.make!uint(1); + } + else + { + // Allocate for the counter and the payload together. + auto p = allocator.allocate(uint.sizeof + T.sizeof); + if (p is null) + { + onOutOfMemoryError(); + } + counter = emplace(cast(uint*) p.ptr, 1); + payload = cast(T*) p[uint.sizeof .. $].ptr; + } + } + + /** + * Increases the reference counter by one. + */ + this(this) pure nothrow @safe @nogc + { + if (isInitialized) + { + ++(*counter); + } + } + + /** + * Decreases the reference counter by one. + * + * If the counter reaches 0, destroys the owned value. + */ + ~this() + { + if (!isInitialized || (--(*counter))) + { + return; + } + allocator.dispose(payload); + payload = null; + + allocator.dispose(counter); + counter = null; + } + + /** + * Takes ownership over $(D_PARAM rhs). Initializes this + * $(D_PSYMBOL RefCounted) if needed. + * + * If it is the last reference of the previously owned object, + * it will be destroyed. + * + * If the allocator wasn't set before, $(D_PSYMBOL theAllocator) will + * be used. If you need a different allocator, create a new + * $(D_PSYMBOL RefCounted). + * + * Params: + * rhs = Object whose ownership is taken over. + */ + ref T opAssign(T rhs) + { + checkAllocator(); + if (isInitialized) + { + static if (isReference!T) + { + if (!--(*counter)) + { + allocator.dispose(payload); + *counter = 1; + } + } + } + else + { + initialize(); + } + move(rhs, get); + return get; + } + + /// Ditto. + ref typeof(this) opAssign(typeof(this) rhs) + { + swap(counter, rhs.counter); + swap(get, rhs.get); + swap(allocator, rhs.allocator); + + return this; + } + + /** + * Defines the casting to the original type. + * + * Params: + * T = Target type. + * + * Returns: Owned value. + */ + inout(T2) opCast(T2)() inout pure nothrow @safe @nogc + if (is(T : T2)) + in + { + assert(payload !is null, "Attempted to access an uninitialized reference."); + } + body + { + return get; + } + + ref inout(T) get() inout return pure nothrow @safe @nogc + { + static if (isReference!T) + { + return payload; + } + else + { + return *payload; + } + } + + /** + * Returns: The number of $(D_PSYMBOL RefCounted) instances that share + * ownership over the same pointer (including $(D_KEYWORD this)). + * If this $(D_PSYMBOL RefCounted) isn't initialized, returns 0. + */ + @property uint count() const pure nothrow @safe @nogc + { + return counter is null ? 0 : *counter; + } + + /** + * Returns: Whether tihs $(D_PSYMBOL RefCounted) is initialized. + */ + @property bool isInitialized() const pure nothrow @safe @nogc + { + return counter !is null; + } + + mixin StructAllocator; + + alias get this; +} + +version (unittest) +{ + class A + { + uint *destroyed; + + this(ref uint destroyed) + { + this.destroyed = &destroyed; + } + + ~this() + { + ++(*destroyed); + } + } + + struct B + { + @disable this(); + this(int param1) + { + } + } +} + +/// +unittest +{ + struct S + { + RefCounted!(ubyte[]) member; + + this(ref ubyte[] member) + { + assert(!this.member.isInitialized); + this.member = member; + assert(this.member.isInitialized); + } + } + + auto arr = theAllocator.makeArray!ubyte(2); + { + auto a = S(arr); + assert(a.member.count == 1); + + void func(S a) + { + assert(a.member.count == 2); + } + func(a); + + assert(a.member.count == 1); + } + // arr is destroyed. +} + +private unittest +{ + uint destroyed; + auto a = theAllocator.make!A(destroyed); + + assert(destroyed == 0); + { + auto rc = RefCounted!A(a); + assert(rc.count == 1); + + void func(RefCounted!A rc) + { + assert(rc.count == 2); + } + func(rc); + + assert(rc.count == 1); + } + assert(destroyed == 1); + + RefCounted!int rc; + rc = 8; +} + +private unittest +{ + auto rc = RefCounted!int(5); + + static assert(is(typeof(rc.payload) == int*)); + static assert(is(typeof(cast(int) rc) == int)); + + static assert(is(typeof(RefCounted!(int*).payload) == int*)); + + static assert(is(typeof(cast(A) (RefCounted!A())) == A)); + static assert(is(typeof(cast(Object) (RefCounted!A())) == Object)); + static assert(!is(typeof(cast(int) (RefCounted!A())))); + + static assert(is(RefCounted!B)); +} + +/** + * Constructs a new object of type $(D_PARAM T) and wraps it in a + * $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for + * the constructor of $(D_PARAM T). + * + * This function is more efficient than using $(D_PSYMBOL RefCounted) + * for the new objects directly, since $(D_PSYMBOL refCounted) allocates + * only once (the object and the internal storage are allocated at once). + * + * Params: + * T = Type of the constructed object. + * A = Types of the arguments to the constructor of $(D_PARAM T). + * args = Constructor arguments of $(D_PARAM T). + * + * Returns: Newly created $(D_PSYMBOL RefCounted!T). + */ +RefCounted!T refCounted(T, A...)(IAllocator allocator, auto ref A args) + if (!is(T == interface) && !isAbstractClass!T) +{ + auto rc = typeof(return)(allocator); + + immutable toAllocate = max(stateSize!T, 1) + uint.sizeof; + auto p = allocator.allocate(toAllocate); + if (p is null) + { + onOutOfMemoryError(); + } + scope (failure) + { + allocator.deallocate(p); + } + + rc.counter = emplace(cast(uint*) p.ptr, 1); + + static if (is(T == class)) + { + rc.payload = emplace!T(p[uint.sizeof .. $], args); + } + else + { + rc.payload = emplace(cast(T*) p[uint.sizeof .. $].ptr, args); + } + + return rc; +} + +/// +unittest +{ + auto rc = theAllocator.refCounted!int(5); + assert(rc.count == 1); + + void func(RefCounted!int param) + { + if (param.count == 2) + { + func(param); + } + else + { + assert(param.count == 3); + } + } + func(rc); + + assert(rc.count == 1); +} + +private unittest +{ + static assert(!is(theAllocator.refCounted!A)); + static assert(!is(typeof(theAllocator.refCounted!B()))); + static assert(is(typeof(theAllocator.refCounted!B(5)))); +} diff --git a/source/tanya/traits.d b/source/tanya/traits.d new file mode 100644 index 0000000..74e18be --- /dev/null +++ b/source/tanya/traits.d @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.traits; + +import std.traits; + +/** + * Params: + * T = Type. + * + * Returns: $(D_KEYWORD true) if $(D_PARAM T) is a reference type or a pointer, + * $(D_KEYWORD false) otherwise. + */ +enum bool isReference(T) = isDynamicArray!T || isPointer!T + || is(T == class) || is(T == interface);