Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vireo ResizeArray implementation #464

Merged
merged 4 commits into from
Jun 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions source/core/CEntryPoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,20 +279,29 @@ VIREO_EXPORT Int32 EggShell_GetArrayDimLength(TypeManagerRef tm, const char* viN
}
//------------------------------------------------------------
//! Resizes a variable size Array symbol to have new dimension lengths specified by newLengths, it also initializes cells for non-flat data.
//! Returns -1 if the symbols is not found, -2 if was not possible to resize the array and 0 if resizing was successful.
VIREO_EXPORT Int32 EggShell_ResizeArray(TypeManagerRef tm, const char* viName, const char* eltName, Int32 rank, Int32* newLengths)
VIREO_EXPORT EggShellResult EggShell_ResizeArray(TypeManagerRef tm, const TypeRef actualType, const void* pData,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider const also for TypeManagerRef. Also, add at least null checks for tm. We might need to update other API calls to validate tm.

Copy link
Contributor

@gleono gleono Jun 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the other functions in CEntryPoints.cpp use const for the TypeManagerRef. Maybe we can submit that as a separate change before merging ni:api-type-refactor to master.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made an issue to track things we should verify across all the implemented functions before merging. Tracked as "Consistent marking of parameters as const on the CEntryPoints" in #466

Int32 newDimensionsLength, Int32 newDimensions[])
{
SubString objectName(viName);
SubString path(eltName);
void *pData = nullptr;
TypeManagerScope scope(tm);
if (actualType == nullptr || !actualType->IsValid()) {
return kEggShellResult_InvalidTypeRef;
}

TypeRef actualType = tm->GetObjectElementAddressFromPath(&objectName, &path, &pData, true);
if (actualType == nullptr || !actualType->IsArray()) {
return kLVError_ArgError;
if (!actualType->IsArray()) {
return kEggShellResult_UnexpectedObjectType;
}

TypedArrayCoreRef actualArray = *(TypedArrayCoreRef*)pData;
return Data_ResizeArray(tm, actualArray, rank, newLengths);
if (actualType->Rank() != newDimensionsLength) {
return kEggShellResult_MismatchedArrayRank;
}

TypedArrayCoreRef arrayObject = *(TypedArrayCoreRef*)pData;
VIREO_ASSERT(TypedArrayCore::ValidateHandle(arrayObject));

if (!arrayObject->ResizeDimensions(newDimensionsLength, newDimensions, true, false)) {
return kEggShellResult_UnableToCreateReturnBuffer;
}
return kEggShellResult_Success;
}
//------------------------------------------------------------
VIREO_EXPORT void* Data_GetStringBegin(StringRef stringObject)
Expand Down Expand Up @@ -361,6 +370,7 @@ VIREO_EXPORT void* Data_GetArrayBegin(const void* pData)
}
//------------------------------------------------------------
//! Get the values for dimensions of the array. Assumes dimensions target is of length equal to rank
//! Caller is expected to allocate an array dimensions of size array rank for the duration of function invocation.
VIREO_EXPORT void Data_GetArrayDimensions(const void* pData, IntIndex dimensions[])
{
TypedArrayCoreRef arrayObject = *(TypedArrayCoreRef*)pData;
Expand Down
5 changes: 3 additions & 2 deletions source/include/CEntryPoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ typedef enum {
kEggShellResult_InvalidResultPointer = 3,
kEggShellResult_UnableToCreateReturnBuffer = 4,
kEggShellResult_InvalidTypeRef = 5,
kEggShellResult_MismatchedArrayRank = 6,
} EggShellResult;
//------------------------------------------------------------
//! TypeManager functions
Expand All @@ -47,8 +48,8 @@ VIREO_EXPORT const char* EggShell_ReadValueString(TypeManagerRef tm, const char*
VIREO_EXPORT EggShellResult EggShell_GetPointer(TypeManagerRef tm,
const char* viName, const char* elementName, void** dataPointer, void** typePointer);
VIREO_EXPORT Int32 EggShell_GetArrayDimLength(TypeManagerRef tm, const char* viName, const char* eltName, Int32 dim);
VIREO_EXPORT Int32 EggShell_ResizeArray(TypeManagerRef tm, const char* viName, const char* eltName,
Int32 rank, Int32* newLengths);
VIREO_EXPORT EggShellResult EggShell_ResizeArray(TypeManagerRef tm, const TypeRef actualType, const void* pData,
Int32 newDimensionsLength, Int32 newDimensions[]);
VIREO_EXPORT EggShellResult Data_ValidateArrayType(TypeManagerRef tm, TypeRef typeRef);
VIREO_EXPORT void* Data_GetStringBegin(StringRef stringObject);
VIREO_EXPORT Int32 Data_GetStringLength(StringRef stringObject);
Expand Down
45 changes: 27 additions & 18 deletions source/io/module_eggShell.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
'EggShell_WriteValueString',
'EggShell_GetPointer',
'EggShell_GetArrayDimLength',
'EggShell_ResizeArray',
'Data_ValidateArrayType',
'Data_GetStringBegin',
'Data_GetStringLength',
Expand Down Expand Up @@ -85,7 +84,8 @@
UNEXPECTED_OBJECT_TYPE: 2,
INVALID_RESULT_POINTER: 3,
UNABLE_TO_CREATE_RETURN_BUFFER: 4,
INVALID_TYPE_REF: 5
INVALID_TYPE_REF: 5,
MISMATCHED_ARRAY_RANK: 6
};
var eggShellResultEnum = {};
eggShellResultEnum[EGGSHELL_RESULT.SUCCESS] = 'Success';
Expand All @@ -94,6 +94,7 @@
eggShellResultEnum[EGGSHELL_RESULT.INVALID_RESULT_POINTER] = 'InvalidResultPointer';
eggShellResultEnum[EGGSHELL_RESULT.UNABLE_TO_CREATE_RETURN_BUFFER] = 'UnableToCreateReturnBuffer';
eggShellResultEnum[EGGSHELL_RESULT.INVALID_TYPE_REF] = 'InvalidTypeRef';
eggShellResultEnum[EGGSHELL_RESULT.MISMATCHED_ARRAY_RANK] = 'MismatchedArrayRank';

// Keep in sync with NIError in DataTypes.h
var niErrorEnum = {
Expand All @@ -115,7 +116,6 @@
var EggShell_WriteValueString = Module.cwrap('EggShell_WriteValueString', 'void', ['number', 'string', 'string', 'string', 'string']);
var EggShell_GetPointer = Module.cwrap('EggShell_GetPointer', 'number', ['number', 'string', 'string', 'number', 'number']);
var EggShell_GetArrayDimLength = Module.cwrap('EggShell_GetArrayDimLength', 'number', ['number', 'string', 'string', 'number']);
var EggShell_ResizeArray = Module.cwrap('EggShell_ResizeArray', 'number', ['number', 'string', 'string', 'number', 'number']);
var Data_ValidateArrayType = Module.cwrap('Data_ValidateArrayType', 'number', ['number', 'number']);
var Data_GetStringBegin = Module.cwrap('Data_GetStringBegin', 'number', []);
var Data_GetStringLength = Module.cwrap('Data_GetStringLength', 'number', []);
Expand Down Expand Up @@ -425,8 +425,7 @@
};

Module.eggShell.getArrayDimensions = publicAPI.eggShell.getArrayDimensions = function (valueRef) {
var TypedArrayConstructor = findCompatibleTypedArrayConstructor(valueRef.typeRef);
if (TypedArrayConstructor === undefined) {
if (!Module.typeHelpers.isArray(valueRef.typeRef)) {
throw new Error('Performing getArrayDimensions failed for the following reason: ' + eggShellResultEnum[EGGSHELL_RESULT.UNEXPECTED_OBJECT_TYPE] +
' (error code: ' + EGGSHELL_RESULT.UNEXPECTED_OBJECT_TYPE + ')' +
' (typeRef: ' + valueRef.typeRef + ')' +
Expand Down Expand Up @@ -572,20 +571,30 @@
return EggShell_GetArrayDimLength(Module.eggShell.v_userShell, vi, path, dim);
};

Module.eggShell.resizeArray = publicAPI.eggShell.resizeArray = function (vi, path, newDimensionSizes) {
var int32Byte = 4;
var rank = newDimensionSizes.length;
var newLengths = Module._malloc(rank * int32Byte);

for (var i = 0; i < rank; i += 1) {
Module.setValue(newLengths + (i * int32Byte), newDimensionSizes[i], 'i32');
Module.eggShell.resizeArray = publicAPI.eggShell.resizeArray = function (valueRef, newDimensions) {
if (!Array.isArray(newDimensions)) {
throw new Error('Expected newDimensions to be an array of dimension lengths, instead got: ' + newDimensions);
}

var success = EggShell_ResizeArray(Module.eggShell.v_userShell, vi, path, rank, newLengths);

Module._free(newLengths);

return success;
var stack = Module.stackSave();
var newDimensionsLength = newDimensions.length;
var dimensionsPointer = Module.stackAlloc(newDimensionsLength * LENGTH_SIZE);
var i, currentDimension;
for (i = 0; i < newDimensionsLength; i += 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it common practice is JS to use i += 1, instead of i++?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a linting rule in .eslintrc.js. Why is preferred? Because ++ are subject to auto-semicolon insertion. Checkout eslint docs. If we really don't like it, there's also the exception "allowForLoopAfterthoughts": true

currentDimension = newDimensions[i];

if (typeof currentDimension !== 'number') {
throw new Error('Expected all dimensions of newDimensions to be numeric values for dimension length, instead got' + currentDimension);
}
Module.setValue(dimensionsPointer + (i * LENGTH_SIZE), currentDimension, 'i32');
}
var eggShellResult = Module._EggShell_ResizeArray(Module.eggShell.v_userShell, valueRef.typeRef, valueRef.dataRef, newDimensionsLength, dimensionsPointer);
if (eggShellResult !== 0) {
throw new Error('Resizing the array failed for the following reason: ' + eggShellResultEnum[eggShellResult] +
' (error code: ' + eggShellResult + ')' +
' (typeRef: ' + valueRef.typeRef + ')' +
' (dataRef: ' + valueRef.dataRef + ')');
}
Module.stackRestore(stack);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if any of the Errors thrown above would cause a memory leak?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Written under the assumption that on exception throw to JS the whole Vireo instance (stack + heap) should be considered corrupt.

We should verify that assumption is usable and standardize across functions prior to merge. Tracked as "Robustness of stackSave / restore and error recovery the api should enable" in #466

};

Module.eggShell.dataReadString = function (stringPointer) {
Expand Down
1 change: 1 addition & 0 deletions test-it/ViaTests/ArrayDemo.via
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ define(ArrayDemo dv(.VirtualInstrument (

e(a(.Int32 * *) variableArray2d)
e(a(.Int32 2 3) fixedArray2d)
e(a(.Int32 0 0) fixedArray2dEmpty)
e(a(.Int32 1 2 3) fixedArray3d)
)
clump(1
Expand Down
1 change: 1 addition & 0 deletions test-it/karma/publicapi/GetArrayDimensions.Test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('The Vireo EggShell getArrayDimensions api', function () {
expect(getArrayDimensions('fixedArray1dwithDefaults')).toEqual([5]);
expect(getArrayDimensions('boundedArray1dwithDefaults')).toEqual([4]);
expect(getArrayDimensions('fixedArray2d')).toEqual([2, 3]);
expect(getArrayDimensions('fixedArray2dEmpty')).toEqual([0, 0]);
expect(getArrayDimensions('fixedArray3d')).toEqual([1, 2, 3]);
done();
});
Expand Down
156 changes: 103 additions & 53 deletions test-it/karma/publicapi/ResizeArray.Test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,84 +5,134 @@ describe('Arrays in Vireo', function () {
var vireoRunner = window.testHelpers.vireoRunner;
var fixtures = window.testHelpers.fixtures;

// Sharing Vireo instances across tests make them run soooo much faster
var vireo = new Vireo();
var viaPath = fixtures.convertToAbsoluteFromViaTestsDir('ArrayDemo.via');
var viName = 'ArrayDemo';
var testsArrayDemoViaUrl = fixtures.convertToAbsoluteFromViaTestsDir('ArrayDemo.via');
var publicApiMultipleTypesViaUrl = fixtures.convertToAbsoluteFromFixturesDir('publicapi/MultipleTypes.via');
var runSlicesAsync;

beforeEach(function () {
runSlicesAsync = vireoRunner.rebootAndLoadVia(vireo, viaPath);
var resizeArrayHelper = function (viName, path, dimensions) {
var valueRef = vireo.eggShell.findValueRef(viName, path);
vireo.eggShell.resizeArray(valueRef, dimensions);
};

var getArrayDimensionsHelper = function (viName, path) {
var valueRef = vireo.eggShell.findValueRef(viName, path);
return vireo.eggShell.getArrayDimensions(valueRef);
};

beforeAll(function (done) {
fixtures.preloadAbsoluteUrls([
testsArrayDemoViaUrl,
publicApiMultipleTypesViaUrl
], done);
});

it('can be resized when they are variable size 1D array', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();
describe('when using array types', function () {
var viName = 'ArrayDemo';
var resizeArray = resizeArrayHelper.bind(undefined, viName);
var getArrayDimensions = getArrayDimensionsHelper.bind(undefined, viName);

var resized = vireo.eggShell.resizeArray(viName, 'variableArray1d', [5]);
expect(resized).toBe(0);
expect(vireo.eggShell.getArrayDimLength(viName, 'variableArray1d', 0)).toBe(5);
done();
beforeAll(function () {
runSlicesAsync = vireoRunner.rebootAndLoadVia(vireo, testsArrayDemoViaUrl);
});
});

it('can be resized when they are 1d array with defaults', function (done) {
var variableName = 'variableArray1dwithDefaults';
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();
it('can be resized when they are variable size 1D array', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

resizeArray('variableArray1d', [5]);
expect(getArrayDimensions('variableArray1d')).toEqual([5]);
done();
});
});

var resized = vireo.eggShell.resizeArray(viName, variableName, [6]);
it('can be resized when they are 1d array with defaults', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

expect(resized).toBe(0);
expect(vireo.eggShell.getArrayDimLength(viName, variableName, 0)).toBe(6);
resizeArray('variableArray1dwithDefaults', [6]);
expect(getArrayDimensions('variableArray1dwithDefaults')).toEqual([6]);

done();
done();
});
});
});

it('returns error code 1 if the array does not exist', function (done) {
var variableName = 'imaginaryArrayThatDoesNotExist';
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();
it('cannot be resized if they are fixed 2d arrays', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

expect(resizeArray.bind(undefined, 'fixedArray2d', [3, 4])).toThrowError(/UnableToCreateReturnBuffer/);

var resized = vireo.eggShell.resizeArray(viName, variableName, [6]);
expect(resized).toBe(1);
done();
done();
});
});
});

it('cannot be resized if they are fixed 2d arrays', function (done) {
var variableName = 'fixedArray2d';
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();
it('can be resized when they are variable 2d array', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

resizeArray('variableArray2d', [3, 4]);
expect(getArrayDimensions('variableArray2d')).toEqual([3, 4]);

var resized = vireo.eggShell.resizeArray(viName, variableName, [3, 4]);
done();
});
});

expect(resized).toBe(2);
expect(vireo.eggShell.getArrayDimLength(viName, variableName, 0)).toBe(2);
expect(vireo.eggShell.getArrayDimLength(viName, variableName, 1)).toBe(3);
it('throws for dimension arrays that are invalid arrays', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

done();
expect(resizeArray.bind(undefined, 'variableArray2d', 'notAnArray')).toThrowError(/to be an array/);

done();
});
});

it('throws for dimension arrays with non-numeric dimensions', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

expect(resizeArray.bind(undefined, 'variableArray2d', ['pen', 'pineapple'])).toThrowError(/numeric values/);

done();
});
});

it('throws for new dimensions with wrong rank', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();

expect(resizeArray.bind(undefined, 'variableArray2d', [1])).toThrowError(/MismatchedArrayRank/);

done();
});
});
});

it('can be resized when they are variable 2d array', function (done) {
var variableName = 'variableArray2d';
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeNonEmptyString();
expect(rawPrintError).toBeEmptyString();
describe('when using non-array types', function () {
var viName = 'MyVI';
var resizeArray = resizeArrayHelper.bind(undefined, viName);

beforeAll(function () {
runSlicesAsync = vireoRunner.rebootAndLoadVia(vireo, publicApiMultipleTypesViaUrl);
});

var resized = vireo.eggShell.resizeArray(viName, variableName, [3, 4]);
it('will throw for a boolean type', function (done) {
runSlicesAsync(function (rawPrint, rawPrintError) {
expect(rawPrint).toBeEmptyString();
expect(rawPrintError).toBeEmptyString();

expect(resized).toBe(0);
expect(vireo.eggShell.getArrayDimLength(viName, variableName, 0)).toBe(3);
expect(vireo.eggShell.getArrayDimLength(viName, variableName, 1)).toBe(4);
expect(resizeArray.bind(undefined, 'dataItem_Boolean', [3, 4])).toThrowError(/UnexpectedObjectType/);

done();
done();
});
});
});
});