Make RefCounted work with dynamic arrays

This commit is contained in:
Eugen Wissner 2017-04-16 20:14:04 +02:00
parent 7aa9ac9f4a
commit 628153e2e8

View File

@ -14,34 +14,16 @@ import core.exception;
import std.algorithm.comparison; import std.algorithm.comparison;
import std.algorithm.mutation; import std.algorithm.mutation;
import std.conv; import std.conv;
import std.range;
import std.traits; import std.traits;
import tanya.memory; import tanya.memory;
/** package(tanya) final class RefCountedStore(T)
* 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 (is(T == class) || is(T == interface)) T payload;
{ size_t counter = 1;
private alias Payload = T;
}
else
{
private alias Payload = T*;
}
private class Storage size_t opUnary(string op)()
{
private Payload payload;
private size_t counter = 1;
private final size_t opUnary(string op)() pure nothrow @safe @nogc
if (op == "--" || op == "++") if (op == "--" || op == "++")
in in
{ {
@ -52,7 +34,7 @@ struct RefCounted(T)
mixin("return " ~ op ~ "counter;"); mixin("return " ~ op ~ "counter;");
} }
private final int opCmp(size_t counter) const pure nothrow @safe @nogc int opCmp(size_t counter)
{ {
if (this.counter > counter) if (this.counter > counter)
{ {
@ -68,37 +50,55 @@ struct RefCounted(T)
} }
} }
private final int opEquals(size_t counter) const pure nothrow @safe @nogc int opEquals(size_t counter)
{ {
return this.counter == counter; return this.counter == counter;
} }
} }
private final class RefCountedStorage : Storage private void separateDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{ {
private shared Allocator allocator; allocator.dispose(storage.payload);
allocator.dispose(storage);
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
} }
~this() nothrow @nogc private void unifiedDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{ {
allocator.dispose(payload); auto ptr1 = finalize(storage);
auto ptr2 = finalize(storage.payload);
allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]);
} }
/**
* 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 (is(T == class) || is(T == interface) || isArray!T)
{
private alias Payload = T;
} }
else
{
private alias Payload = T*;
}
private alias Storage = RefCountedStore!Payload;
private Storage storage; private Storage storage;
private void function(Storage storage,
shared Allocator allocator) @nogc deleter;
invariant invariant
{ {
assert(storage is null || allocator_ !is null); assert(storage is null || allocator_ !is null);
assert(storage is null || deleter !is null);
} }
/** /**
@ -112,11 +112,13 @@ struct RefCounted(T)
* *
* Precondition: $(D_INLINECODE allocator !is null) * Precondition: $(D_INLINECODE allocator !is null)
*/ */
this(Payload value, shared Allocator allocator = defaultAllocator) this()(auto ref Payload value,
shared Allocator allocator = defaultAllocator)
{ {
this(allocator); this(allocator);
storage = allocator.make!RefCountedStorage(allocator); this.storage = allocator.make!Storage();
move(value, storage.payload); this.deleter = &separateDeleter!Payload;
move(value, this.storage.payload);
} }
/// Ditto. /// Ditto.
@ -148,9 +150,9 @@ struct RefCounted(T)
*/ */
~this() ~this()
{ {
if (storage !is null && !(storage.counter && --storage)) if (this.storage !is null && !(this.storage.counter && --this.storage))
{ {
allocator_.dispose(storage); deleter(storage, allocator);
} }
} }
@ -170,54 +172,48 @@ struct RefCounted(T)
* Params: * Params:
* rhs = $(D_KEYWORD this). * rhs = $(D_KEYWORD this).
*/ */
ref typeof(this) opAssign(Payload rhs) ref typeof(this) opAssign()(auto ref Payload rhs)
{ {
if (storage is null) if (this.storage is null)
{ {
storage = allocator.make!RefCountedStorage(allocator); this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!Payload;
} }
else if (storage > 1) else if (this.storage > 1)
{ {
--storage; --this.storage;
storage = allocator.make!RefCountedStorage(allocator); this.storage = allocator.make!Storage();
} this.deleter = &separateDeleter!Payload;
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
storage = allocator.make!RefCountedStorage(allocator);
} }
else else
{ {
allocator.dispose(storage.payload); // Created with refCounted. Always destroyed togethter with the pointer.
assert(this.storage.counter != 0);
finalize(this.storage.payload);
this.storage.payload = Payload.init;
} }
move(rhs, storage.payload); move(rhs, this.storage.payload);
return this; return this;
} }
/// Ditto. /// Ditto.
ref typeof(this) opAssign(typeof(null)) ref typeof(this) opAssign(typeof(null))
{ {
if (storage is null) if (this.storage is null)
{ {
return this; return this;
} }
else if (storage > 1) else if (this.storage > 1)
{ {
--storage; --this.storage;
storage = null; this.storage = null;
}
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
return this;
} }
else else
{ {
allocator.dispose(storage.payload); // Created with refCounted. Always destroyed togethter with the pointer.
assert(this.storage.counter != 0);
finalize(this.storage.payload);
this.storage.payload = Payload.init;
} }
return this; return this;
} }
@ -225,8 +221,9 @@ struct RefCounted(T)
/// Ditto. /// Ditto.
ref typeof(this) opAssign(typeof(this) rhs) ref typeof(this) opAssign(typeof(this) rhs)
{ {
swap(allocator_, rhs.allocator_); swap(this.allocator_, rhs.allocator_);
swap(storage, rhs.storage); swap(this.storage, rhs.storage);
swap(this.deleter, rhs.deleter);
return this; return this;
} }
@ -380,15 +377,22 @@ private unittest
* args = Constructor arguments of $(D_PARAM T). * args = Constructor arguments of $(D_PARAM T).
* *
* Returns: Newly created $(D_PSYMBOL RefCounted!T). * Returns: Newly created $(D_PSYMBOL RefCounted!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/ */
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T if (!is(T == interface) && !isAbstractClass!T
&& !isArray!T && !isAssociativeArray!T) && !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
{ {
auto rc = typeof(return)(allocator); auto rc = typeof(return)(allocator);
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage)); const storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
immutable size = alignedSize(stateSize!T + storageSize); const size = alignedSize(stateSize!T + storageSize);
auto mem = (() @trusted => allocator.allocate(size))(); auto mem = (() @trusted => allocator.allocate(size))();
if (mem is null) if (mem is null)
@ -399,7 +403,7 @@ RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
{ {
() @trusted { allocator.deallocate(mem); }(); () @trusted { allocator.deallocate(mem); }();
} }
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]); rc.storage = emplace!((RefCounted!T.Storage))(mem[0 .. storageSize]);
static if (is(T == class)) static if (is(T == class))
{ {
@ -410,9 +414,38 @@ RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))(); auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args); rc.storage.payload = emplace!T(ptr, args);
} }
rc.deleter = &unifiedDeleter!(RefCounted!T.Payload);
return rc; return rc;
} }
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL RefCounted) using.
*
* Params:
* T = Array type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
*/
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = cast(T) allocator.allocate(ElementType!T.sizeof * size);
initializeAll(payload);
return RefCounted!T(payload, allocator);
}
/// ///
unittest unittest
{ {
@ -456,3 +489,9 @@ private @nogc unittest
assert(rc.count); assert(rc.count);
} }
} }
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5);
}