reign
Version:
A persistent, typed-objects implementation.
228 lines (209 loc) • 6.71 kB
JavaScript
/* @flow */
import {TypedObject} from "../";
import type {Realm} from "../../";
import type Backing from "backing";
import {
$ValueType,
$Address,
$CanBeEmbedded,
$CanBeReferenced,
$CanContainReferences
} from "../../symbols";
export const MIN_TYPE_ID = Math.pow(2, 20) * 3;
/**
* Makes a ReferenceType type class for the given realm.
*/
export function make (realm: Realm): TypeClass<ReferenceType<any>> {
const {TypeClass} = realm;
let typeCounter = 0;
return new TypeClass('ReferenceType', (Target: Function): Function => {
return (Reference: Function): Object => {
typeCounter++;
const name = typeof Target.name === 'string' && Target.name.length > 0 ? `Reference<${Target.name}>` : `%Reference<0x${typeCounter.toString(16)}>`;
const id = MIN_TYPE_ID + typeCounter;
Reference[$CanBeEmbedded] = true;
Reference[$CanBeReferenced] = false;
Reference[$CanContainReferences] = true;
if (!Target[$CanBeReferenced]) {
throw new TypeError(`Type ${Target.name} cannot be referenced.`);
}
let ReferenceArray;
// @flowIssue 285
Object.defineProperties(Reference, {
Array: {
get () {
if (ReferenceArray === undefined) {
ReferenceArray = new realm.ArrayType(Reference);
}
return ReferenceArray;
}
}
});
/**
* Initialize a reference to the given object at the given address.
*/
function initializeReference (backing: Backing, pointerAddress: float64, value?: Object): void {
if (value == null) {
backing.setFloat64(pointerAddress, 0);
}
else if (value instanceof Target) {
if (!value[$CanBeReferenced]) {
throw new ReferenceError(`Cannot reference value of type ${Target.name}`);
}
const address = value[$Address];
backing.gc.ref(address);
backing.setFloat64(pointerAddress, address);
}
else {
const address = backing.gc.alloc(Target.byteLength, Target.id, 1);
Target.initialize(backing, address, value);
backing.setFloat64(pointerAddress, address);
}
}
/**
* Store a reference to the given object at the given address.
*/
function storeReference (backing: Backing, pointerAddress: float64, value: ?Object): void {
const existing = backing.getFloat64(pointerAddress);
if (value == null) {
if (existing !== 0) {
backing.setFloat64(pointerAddress, 0);
}
return;
}
else if (existing !== 0) {
backing.gc.unref(existing);
}
if (value instanceof Target) {
if (!value[$CanBeReferenced]) {
throw new ReferenceError(`Cannot reference value of type ${Target.name}`);
}
const address = value[$Address];
backing.gc.ref(address);
backing.setFloat64(pointerAddress, address);
}
else {
const address = backing.gc.alloc(Target.byteLength, Target.id, 1);
Target.initialize(backing, address, value);
backing.setFloat64(pointerAddress, address);
}
}
/**
* Load an object based on the reference stored at the given address.
*/
function loadReference (backing: Backing, pointerAddress: float64): ?Target {
const address = backing.getFloat64(pointerAddress);
if (address === 0) {
return null;
}
else {
return Target.load(backing, address);
}
}
/**
* Remove a reference at the given address.
*/
function clearReference (backing: Backing, pointerAddress: float64) {
const address = backing.getFloat64(pointerAddress);
if (address !== 0) {
backing.setFloat64(pointerAddress, 0);
backing.gc.unref(address);
}
}
/**
* Destroy a reference at the given address.
*/
function referenceDestructor (backing: Backing, pointerAddress: float64) {
const address = backing.getFloat64(pointerAddress);
if (address !== 0) {
backing.setFloat64(pointerAddress, 0);
backing.gc.unref(address);
}
}
return {
id,
name,
byteLength: 8,
byteAlignment: 8,
initialize: initializeReference,
store: storeReference,
load: loadReference,
clear: clearReference,
destructor: referenceDestructor,
emptyValue () {
return null;
},
equal (valueA: ?Object, valueB: ?Object): boolean {
if (valueA === valueB) {
return true;
}
else if (!valueA || !valueB) {
return false;
}
else {
return Target.equal(valueA, valueB);
}
},
compareValues (valueA: ?Object, valueB: ?Object): int8 {
return Target.compareValues(valueA, valueB);
},
compareAddresses (backing: Backing, addressA: float64, addressB: float64): int8 {
if (addressA === addressB) {
return 0;
}
else if (addressA === 0) {
return -1;
}
else if (addressB === 0) {
return 1;
}
const pointerA = backing.getFloat64(addressA);
const pointerB = backing.getFloat64(addressB);
if (pointerA === pointerB) {
return 0;
}
else if (pointerA === 0) {
return -1;
}
else if (pointerB === 0) {
return 1;
}
else {
return Target.compareAddresses(backing, pointerA, pointerB);
}
},
compareAddressValue (backing: Backing, address: float64, value: ?Object): int8 {
const pointer = backing.getFloat64(address);
if (pointer === 0) {
if (value == null) {
return 0;
}
else {
return -1;
}
}
return Target.compareAddressValue(backing, pointer, value);
},
randomValue (): any {
if (Math.random() < 0.5) {
return null;
}
else {
return Target.randomValue();
}
},
hashValue (input: any): uint32 {
if (input == null) {
return 4;
}
else {
return Target.hashValue(input);
}
},
flowType () {
return `Reference<${Target.flowType()}>`;
}
};
};
});
};