Skip to content
This repository has been archived by the owner on May 21, 2020. It is now read-only.

Improve demo, simplify user experience #20

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions fractal-terrain-generation/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist/js
dist/index.html
15 changes: 7 additions & 8 deletions fractal-terrain-generation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

Demo originally created for talk given by Ryland Goldstein at _Serverless conf 2018_ in San Francisco.

1. Replace <BINARIS_ACCOUNT_NUMBER> in `dist/index.html` with your account number.
1. Export `BINARIS_ACCOUNT_NUMBER` to your environment
1. Run the following command

`export FRACTAL_ENDPOINT=https://run-sandbox.binaris.com/v2/run/${BINARIS_ACCOUNT_NUMBER}/public_fractal`
1. Run `npm install`
1. Run `bn deploy public_fractal`
1. Run `npm run build`
1. Run `bn deploy public_servePage`
1. If you haven't run `bn login` before, do so
1. Run `npm run install_deps`
1. Run `npm run deploy`
1. Navigate to the generated Binaris function URL for `servePage` in your browser

For even faster generation consider upgrading to the Binaris paid tier.

If you made changes to the frontend (anything in `src`) simply run `npm run deploy_frontend`

For backend changes run `npm run deploy_backend`
67 changes: 67 additions & 0 deletions fractal-terrain-generation/builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const replace = require('replace-in-file');
const fse = require('fs-extra');
const path = require('path');

const YMLUtil = require('binaris/lib/binarisYML');

const { getAccountId, getAPIKey, getRealm } = require('binaris/lib/userConf');
const { forceRealm } = require('binaris/sdk');

const backendYAMLPath = path.join(__dirname, 'fractal_backend');
const servingYAMLPath = path.join(__dirname, 'dist');

async function getServingFuncName() {
const binarisConf = await YMLUtil.loadBinarisConf(servingYAMLPath);
return YMLUtil.getFuncName(binarisConf);
}

async function getBackendFuncName() {
const binarisConf = await YMLUtil.loadBinarisConf(backendYAMLPath);
return YMLUtil.getFuncName(binarisConf);
}

async function getPublicPath(accountID, endpoint) {
process.env.BINARIS_INVOKE_ENDPOINT = endpoint;
Copy link

Choose a reason for hiding this comment

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

Is overriding process.env.BINARIS_INVOKE_ENDPOINT before storing it in savedEndpoint a bug?

Also, this assumes that binaris/sdk/url behaves in a certain way - if you need a specific functionality from the SDK maybe this function should be in the SDK?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. It's not a bug. Our SDK relies on Environment variables and cannot be overridden with passed values. I definitely agree this could prompt a change but that could entail a lengthy discussion. How about we do that in parallel?
  2. It does assume but the alternative is exposing bloat to the user. For now I'm ok with this hack until we fix the underlying issues

Copy link

Choose a reason for hiding this comment

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

Do we still need this whole function with the sandbox shutdown?

const { getInvokeUrl } = require('binaris/sdk/url');
const servingName = await getServingFuncName();
const savedEndpoint = process.env.BINARIS_INVOKE_ENDPOINT;
const invokeURL = await getInvokeUrl(accountID, servingName);
process.env.BINARIS_INVOKE_ENDPOINT = savedEndpoint;
return invokeURL;
}

async function getFractalURL(accountID) {
const { getInvokeUrl } = require('binaris/sdk/url');
const backendName = await getBackendFuncName();
return getInvokeUrl(accountID, backendName);
}

async function replaceHTMLAccountID(accountID) {
const templatePath = path.join(__dirname, 'template.html');
const destPath = path.join(__dirname, 'dist', 'index.html');
await fse.copy(templatePath, destPath, { overwrite: true, errorOnExist: false });

const options = {
files: destPath,
from: /<BINARIS_ACCOUNT_NUMBER>/g,
to: accountID,
};
await replace(options)
}

async function prebuild() {
const realm = await getRealm();
if (realm) {
forceRealm(realm);
}
const accountID = await getAccountId(undefined);
await replaceHTMLAccountID(accountID);
const FRACTAL_ENDPOINT = await getFractalURL(accountID);
const PUBLIC_PATH = (await getPublicPath(accountID, ' ')).slice(8);
Copy link

Choose a reason for hiding this comment

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

Why do we slice(8) the https:// away for webpack? Can we get it through a URL object instead?

return {
FRACTAL_ENDPOINT,
PUBLIC_PATH,
};
}

module.exports = { prebuild };
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
functions:
public_fractal:
file: fractal.js
entrypoint: handler
runtime: node8
public_servePage:
file: servePage.js
entrypoint: handler
Expand Down
15 changes: 15 additions & 0 deletions fractal-terrain-generation/dist/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "fractal-frontend",
"version": "1.0.0",
"description": "",
"main": "servePage.js",
"scripts": {},
"keywords": [],
"author": "Ryland Goldstein",
"license": "MIT",
"dependencies": {
"mime-types": "^2.1.21",
"mz": "^2.7.0",
"path": "^0.12.7"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const fs = require('mz/fs');
const mime = require('mime-types');
const path = require('path');

const prefix = './dist';
const prefix = '.';

exports.handler = async (body, ctx) => {
let resourcePath = ctx.request.path;
Expand All @@ -22,7 +22,8 @@ exports.handler = async (body, ctx) => {
statusCode: 200,
headers: {
'Content-Type': resourceType,
'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
},
body: webpage,
});
Expand Down
65 changes: 0 additions & 65 deletions fractal-terrain-generation/fractal.js

This file was deleted.

5 changes: 5 additions & 0 deletions fractal-terrain-generation/fractal_backend/binaris.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
functions:
public_fractal:
file: fractal.js
entrypoint: handler
runtime: node8
99 changes: 99 additions & 0 deletions fractal-terrain-generation/fractal_backend/fractal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable no-console,no-bitwise */
const noiseGen = require('./noiseGen');
const simplify = require('./simplify');

function makeHeaders(blockCount, maxHeight,
payloadBytes, genTime) {
const statusHeaders = {
'X-Gen-Data-Blockcount': blockCount,
'X-Gen-Data-Max-Height': maxHeight,
'X-Gen-Data-Payload-Bytes': payloadBytes,
'X-Gen-Data-Time-Running-MS': genTime,
};
const customHeaders = {
'Access-Control-Expose-Headers': Object.keys(statusHeaders).join(', '),
'Content-Type': 'application/octet-stream',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin X-Requested-With, Content-Type, Accept',
... statusHeaders,
};
return customHeaders;
}

exports.handler = async (body, ctx) => {
const {
downscale,
heightFactor,
numTex,
size,
xPos,
yPos,
zPos,
} = ctx.request.query;

let respBody;
let headers;
let statusCode = 500;
const genStartTime = process.hrtime();

const { blockCount, data, maxHeight } = noiseGen(
parseInt(xPos, 10), parseInt(yPos, 10),
Copy link

Choose a reason for hiding this comment

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

I think it would be cleaner to have all string->int conversion in the beginning of the handler, (even if some arguments are now passed as strings (numTex, downscale) they work due to conversion, but it's not optimal).

parseInt(zPos, 10), parseInt(size, 10),
numTex, downscale, parseInt(heightFactor, 10)
);
if (blockCount === 0) {
const failedGenTime = process.hrtime(genStartTime);
const genStr = (failedGenTime[0] * 1000) + (failedGenTime[1] / 1000000);
headers = makeHeaders(blockCount, maxHeight, 0, genStr);
respBody = new Buffer(2);
statusCode = 200;
Copy link

Choose a reason for hiding this comment

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

Code-style: my preference would be to have an early-return here (but would require moving into a separate function).

} else {
const { indices, mats, normals, tex, verts } = simplify(
data, size, size, size,
parseInt(xPos, 10), parseInt(yPos, 10),
parseInt(zPos, 10), heightFactor, numTex,
);

// how many elements make up the lookup table
const tableElements = 4;

const mergedData = new Int16Array(verts.length + indices.length
+ normals.length + tex.length + mats.length + tableElements);

// this is an unfortunate bit of logic required to make sure
// that our buffers length value doesn't get truncated/overflowed
// in the lookup table.
function split32BitValue(value) {
return [value & 0xFFFF, (value >> 16) & 0xFFFF];
}
const splitVertsLength = split32BitValue(verts.length);
const splitTexLength = split32BitValue(tex.length);
mergedData[0] = splitVertsLength[0];
mergedData[1] = splitVertsLength[1];
mergedData[2] = splitTexLength[0];
mergedData[3] = splitTexLength[1];

mergedData.set(verts, tableElements);
mergedData.set(normals, tableElements + verts.length);
mergedData.set(tex, tableElements + verts.length + normals.length);
mergedData.set(mats, tableElements + verts.length + normals.length + tex.length);
mergedData.set(indices, verts.length + tableElements + normals.length + tex.length + mats.length);

respBody = Buffer.from(mergedData.buffer);
console.log(`buffer size is ${respBody.length / 1000}`);

const genTime = process.hrtime(genStartTime);
const genDataTimeStr = (genTime[0] * 1000) + (genTime[1] / 1000000);
headers = makeHeaders(blockCount, maxHeight, respBody.length, genDataTimeStr);
statusCode = 200;
}
if (ctx.request.query.express_server) {
Copy link

Choose a reason for hiding this comment

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

What do you think about instead of adding a fake express_server, pass a HTTPResponse constructor and move this to express?

ctx.set(headers);
return ctx.send(respBody);
}
return new ctx.HTTPResponse({
statusCode,
headers,
body: respBody,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ const SimplexNoise = require('simplex-noise');

const simplex = new SimplexNoise('default');

function getMat(currHeight, maxHeight, numColors) {
const heightIncr = maxHeight / numColors;
for (let i = 0; i < numColors; i += 1) {
if (currHeight <= i * heightIncr) {
return i;
}
}
return numColors - 1;
}

/**
* Create a volumetric density field representing the state
* of a given region of terrain.
Expand All @@ -14,7 +24,7 @@ const simplex = new SimplexNoise('default');
* @param {number} scaleHeight - sets the defacto maxHeight of the volume
* @return {object} - generated data and metadata
*/
function createDensities(cX, cZ, size, downscale = 1000, scaleHeight = 50) {
function createDensities(cX, cY, cZ, size, numColors, downscale = 1000, scaleHeight = 50) {
let maxHeight = -1;
let minHeight = 10000;
let blockCount = 0;
Expand Down Expand Up @@ -42,20 +52,34 @@ function createDensities(cX, cZ, size, downscale = 1000, scaleHeight = 50) {
}
}
}
// if the calculated max height isn't divisible by
// the volume size granularity, we need to modify it
// to be an increment of size.
if (maxHeight % size !== 0) {
maxHeight = Math.ceil(maxHeight / size) * size;

if ((minHeight) > (cY + size)) {
Copy link

Choose a reason for hiding this comment

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

Nit: extra parentheses?

return {
blockCount: 0,
data: [],
maxHeight,
minHeight,
};
}
const densities = new Int8Array(size * size * maxHeight).fill(1);

let maxRelevantHeight = maxHeight;
if (maxHeight > (cY + size)) {
maxRelevantHeight = size;
} else {
maxRelevantHeight = maxHeight - cY;
}

const densities = new Int8Array(size * size * size).fill(1);
let currIdx = -1;
for (let i = 0; i < size; i += 1) {
for (let j = 0; j < maxHeight; j += 1) {
for (let j = 0; j < size; j += 1) {
for (let k = 0; k < size; k += 1) {
currIdx = i + (j * size) + (k * maxHeight * size);
densities[currIdx] = (j <= heights[i + (k * size)]) ? 1 : 0;
blockCount += densities[currIdx];
currIdx = i + (j * size) + (k * size * size);
densities[currIdx] = ((j + cY) <= heights[i + (k * size)]) ? 1 : 0;
if (densities[currIdx] !== 0) {
densities[currIdx] = getMat(j + cY, scaleHeight, numColors);
blockCount += 1;
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions fractal-terrain-generation/fractal_backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading