From cda5e94b334dddf139540da1e5dbdbaab370fea7 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Fri, 22 Sep 2017 20:57:18 -0700 Subject: [PATCH] Added JavaScript Baking --- assignment-client/src/assets/AssetServer.cpp | 34 +- .../src/assets/BakeAssetTask.cpp | 5 + libraries/baking/src/JSBaker.cpp | 349 ++++++++++++++++++ libraries/baking/src/JSBaker.h | 57 +++ 4 files changed, 438 insertions(+), 7 deletions(-) create mode 100644 libraries/baking/src/JSBaker.cpp create mode 100644 libraries/baking/src/JSBaker.h diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 9df606c227..78ea1ed7be 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -49,10 +50,11 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; -static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; +static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" , "js" }; static QStringList BAKEABLE_TEXTURE_EXTENSIONS; static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; +static const QString BAKED_SCRIPT_SIMPLE_NAME = "script.js"; void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) { qDebug() << "Starting bake for: " << assetPath << assetHash; @@ -96,7 +98,11 @@ std::pair AssetServer::getAssetStatus(const AssetPath& pa QString bakedFilename; if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; + if (extension == "js") { + bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; + } else { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; } else { @@ -183,7 +189,11 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa } if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; + if (extension == "js") { + bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; + } else { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } } else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) { bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; } else { @@ -485,7 +495,11 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode QString bakedRootFile; if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) { - bakedRootFile = BAKED_MODEL_SIMPLE_NAME; + if (assetPathExtension == "js") { + bakedRootFile = BAKED_SCRIPT_SIMPLE_NAME; + } else { + bakedRootFile = BAKED_MODEL_SIMPLE_NAME; + } } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) { bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME; } @@ -1141,6 +1155,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx"; static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx"; +static const QString BAKED_ASSET_SIMPLE_JS_NAME = "script.js"; QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) { return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath; @@ -1203,8 +1218,9 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina // setup the mapping for this bake file auto relativeFilePath = QUrl(filePath).fileName(); qDebug() << "Relative file path is: " << relativeFilePath; - - if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) { + if (relativeFilePath.endsWith(".js", Qt::CaseInsensitive)) { + relativeFilePath = BAKED_ASSET_SIMPLE_JS_NAME; + } else if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) { // for an FBX file, we replace the filename with the simple name // (to handle the case where two mapped assets have the same hash but different names) relativeFilePath = BAKED_ASSET_SIMPLE_FBX_NAME; @@ -1350,7 +1366,11 @@ bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) { QString bakedFilename; if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; + if (extension == "js") { + bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; + } else { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; } else { diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp index 9073510f79..daff3a3834 100644 --- a/assignment-client/src/assets/BakeAssetTask.cpp +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -15,6 +15,7 @@ #include #include +#include BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) : _assetHash(assetHash), @@ -34,6 +35,10 @@ void BakeAssetTask::run() { _baker = std::unique_ptr { new FBXBaker(QUrl("file:///" + _filePath), fn, PathUtils::generateTemporaryDir()) }; + } else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) { + _baker = std::unique_ptr{ + new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir()) + }; } else { _baker = std::unique_ptr { new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE, diff --git a/libraries/baking/src/JSBaker.cpp b/libraries/baking/src/JSBaker.cpp new file mode 100644 index 0000000000..2c42b342b0 --- /dev/null +++ b/libraries/baking/src/JSBaker.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "JSBaker.h" +#include "Baker.h" + +JSBaker::JSBaker(const QUrl& jsURL, const QString& bakedOutputDir) : + _jsURL(jsURL), + _bakedOutputDir(bakedOutputDir) +{ + +}; + +void JSBaker::bake() { + + auto tempDir = PathUtils::generateTemporaryDir(); + + if (tempDir.isEmpty()) { + handleError("Failed to create a temporary directory."); + return; + } + + _tempDir = tempDir; + + _originalJSFilePath = _tempDir.filePath(_jsURL.fileName()); + qDebug() << "Made temporary dir " << _tempDir; + qDebug() << "Origin file path: " << _originalJSFilePath; + + connect(this, &JSBaker::sourceCopyReadyToLoad, this, &JSBaker::bakeSourceCopy); + + loadSourceJS(); +} + +void JSBaker::loadSourceJS() { + + // load the local file + QFile localJS{ _jsURL.toLocalFile() }; + + if (!localJS.exists()) { + + handleError("Could not find " + _jsURL.toString()); + return; + } + + // copy to original file + qDebug() << "Local file url: " << _jsURL << _jsURL.toString() << _jsURL.toLocalFile() << ", copying to: " << _originalJSFilePath; + localJS.copy(_originalJSFilePath); + + // emit signal to indicate script is ready to import + emit sourceCopyReadyToLoad(); +} + +void JSBaker::bakeSourceCopy() { + + importScript(); + + if (hasErrors()) { + return; + } + + /*bakeScript(); + if (hasErrors()) { + return; + }*/ + + /*exportScript(); + + if (hasErrors()) { + return; + }*/ +} + + + +void JSBaker::importScript() { + + //qDebug() << "file path: " << _originalJSFilePath.toLocal8Bit().data() << QDir(_originalJSFilePath).exists(); + + // Import the file to be processed + QFile jsFile(_originalJSFilePath); + if (!jsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + handleError("Error opening " + _originalJSFilePath + " for reading"); + return; + } + + // Call Bake Script with the opened file + bakeScript(&jsFile); +} + +void JSBaker::bakeScript(QFile* inFile) { + + QFile outFile; + outFile.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream in(inFile); + QTextStream out(&outFile); + + QChar currentChar; + QChar prevChar; + QChar nextChar; + + // Initialize prevChar with new line + prevChar = '\n'; + in >> currentChar; + + while (!in.atEnd()) { + + // Reading per character + in >> nextChar; + + if (currentChar == '\r') { + out << '\n'; + //currentChar = '\n'; + + } else if (currentChar == '/') { + + if (nextChar == '/') { + + handleSingleLineComments(&in); + //out << '\n'; + + //Start fresh after handling comments + prevChar = '\n'; + in >> currentChar; + continue; + + } else if (nextChar == '*') { + + handleMultiLineComments(&in); + //out << ' '; + + //Start fresh after handling comments + prevChar = '\n'; + in >> currentChar; + //currentChar = ' '; + continue; + } else { + out << currentChar; + } + } else if (currentChar == ' ' || (int) currentChar.toLatin1() == 9) { + + // Handle multiple spaces + if (nextChar == ' ' || (int)currentChar.toLatin1() == 9) { + while (nextChar == ' ' || (int)nextChar.toLatin1() == 9 ) { + + in >> nextChar; + if (nextChar == '\n') + break; + } + } + if (!omitSpace(prevChar,nextChar)) { + + out << ' '; + } + + } else if (currentChar == '\n') { + qDebug() << "prevChar2" << prevChar; + qDebug() << "currentChar2" << currentChar; + qDebug() << "nextChar2" << nextChar; + //Handle multiple new lines + //Hnadle new line followed by space or tab + if (nextChar == '\n' || nextChar == ' ' || (int)nextChar.toLatin1() == 9) { + while (nextChar == '\n' || nextChar == ' ' || (int)nextChar.toLatin1() == 9) { + in >> nextChar; + } + } + + if (!omitNewLine(prevChar, nextChar)) { + out << '\n'; + } + + } else if (currentChar == '"' ) { + //Don't modify quoted strings + out << currentChar; + out << nextChar; + while (nextChar != '"') { + in >> nextChar; + out << nextChar; + } + + + //Start fresh after handling quoted strings + //prevChar = '"'; + prevChar = nextChar; + //currentChar = nextChar; + in >> currentChar; + continue; + } else if (currentChar == "'") { + //Don't modify quoted strings + out << currentChar; + out << nextChar; + while (nextChar != "'") { + in >> nextChar; + out << nextChar; + } + + qDebug() << "prevChar" << prevChar; + qDebug() << "currentChar" << currentChar; + qDebug() << "nextChar" << nextChar; + + //out << nextChar; + //Start fresh after handling quoted strings + //prevChar = '\''; + prevChar = nextChar; + //currentChar = nextChar; + in >> currentChar; + qDebug() << "prevChar1" << prevChar; + qDebug() << "currentChar1" << currentChar; + qDebug() << "nextChar1" << nextChar; + continue; + } else { + out << currentChar; + + } + + prevChar = currentChar; + currentChar = nextChar; + } + + //Output current character when next character reaches EOF + if (currentChar != '\n') { + + out << currentChar; + } + + + inFile->close(); + exportScript(&outFile); +} + +void JSBaker::handleSingleLineComments(QTextStream * in) { + QChar ch; + + while (!in->atEnd()) { + *in >> ch; + if (ch <= '\n') + break; + } + + //*out << '\n'; +} + +void JSBaker::handleMultiLineComments(QTextStream * in) { + QChar ch; + + while (!in->atEnd()) { + *in >> ch; + if (ch == '*') { + if (in->read(1) == '/') { + return; + } + } + } + + handleError("Eror unterminated multi line comment"); + +} + +bool JSBaker::isAlphanum(QChar c) { + return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || + c > 126); +} + +bool JSBaker::omitSpace(QChar prevChar, QChar nextChar) { + + if ((isAlphanum(prevChar) || isNonAscii(prevChar) || isSpecialChar(prevChar)) && + (isAlphanum(nextChar) || isNonAscii(nextChar) || isSpecialChar(nextChar)) + ) { + + return false; + } + + return true; + +} + +bool JSBaker::isNonAscii(QChar c) { + return ((int)c.toLatin1() > 127); +} + +bool JSBaker::isSpecialChar(QChar c) { + + if (c == '\'' || c == '$' || c == '_') + return true; + + return false; +} + +bool JSBaker::isSpecialCharPre(QChar c) { + + if (c == '\'' || c == '$' || c == '_' || c == '}' || c == ']' || c == ')' || c == '+' || c == '-' + || c == '"' || c == "'") + return true; + + return false; +} + +bool JSBaker::isSpecialCharPost(QChar c) { + + if (c == '\'' || c == '$' || c == '_' || c == '{' || c == '[' || c == '(' || c == '+' || c == '-' || c == '/') + return true; + + return false; +} + +bool JSBaker::omitNewLine(QChar prevChar, QChar nextChar) { + + if ((isAlphanum(prevChar) || isNonAscii(prevChar) || isSpecialCharPre(prevChar)) && + (isAlphanum(nextChar) || isNonAscii(nextChar) || isSpecialCharPost(nextChar)) + ) { + + return false; + } + + return true; + +} + +void JSBaker::exportScript(QFile* bakedFile) { + // save the relative path to this FBX inside our passed output folder + auto fileName = _jsURL.fileName(); + auto baseName = fileName.left(fileName.lastIndexOf('.')); + auto bakedFilename = baseName + BAKED_JS_EXTENSION; + + _bakedJSFilePath = _bakedOutputDir + "/" + bakedFilename; + + bakedFile->setFileName(_bakedJSFilePath); + + //QFile bakedFile(_bakedJSFilePath); + + if (!bakedFile->open(QIODevice::WriteOnly)) { + handleError("Error opening " + _bakedJSFilePath + " for writing"); + return; + } + + /*QByteArray x(5, 'a'); + bakedFile.copy();*/ + + _outputFiles.push_back(_bakedJSFilePath); + + qCDebug(model_baking) << "Exported" << _jsURL << "with re-written paths to" << _bakedJSFilePath; + bakedFile->close(); + emit finished(); +} \ No newline at end of file diff --git a/libraries/baking/src/JSBaker.h b/libraries/baking/src/JSBaker.h new file mode 100644 index 0000000000..0fb593bd92 --- /dev/null +++ b/libraries/baking/src/JSBaker.h @@ -0,0 +1,57 @@ +#ifndef hifi_JSBaker_h +#define hifi_JSBaker_h + +#include +#include +#include + +#include "Baker.h" +#include "TextureBaker.h" +#include "ModelBakingLoggingCategory.h" + +static const QString BAKED_JS_EXTENSION = ".baked.js"; + +class JSBaker : public Baker { + Q_OBJECT + +public: + JSBaker(const QUrl& jsURL, const QString& bakedOutputDir); + +public slots: + virtual void bake() override; + +signals: + void sourceCopyReadyToLoad(); + +private slots: + void bakeSourceCopy(); + + +private : + + QUrl _jsURL; + QString _bakedOutputDir; + QDir _tempDir; + QString _originalJSFilePath; + QString _bakedJSFilePath; + QByteArray _buffer; + + void loadSourceJS(); + void importScript(); + + void exportScript(QFile*); + void bakeScript(QFile*); + void handleSingleLineComments(QTextStream*); + void handleMultiLineComments(QTextStream*); + + bool isAlphanum(QChar); + bool omitSpace(QChar, QChar); + bool isNonAscii(QChar c); + bool isSpecialChar(QChar c); + bool isSpecialCharPre(QChar c); + bool isSpecialCharPost(QChar c); + bool omitNewLine(QChar, QChar); + +}; + +#endif // !hifi_JSBaker_h \ No newline at end of file