Exemple #1
0
void LSCompiler::initialize()
{
    AssemblyReader::addLibraryAssemblyPath("./libs");

    if (rootBuildFile.length() != 0)
    {
        rootBuildInfo = loadBuildFile(rootBuildFile);
    }
    else
    {
        rootBuildInfo = BuildInfo::createDefaultBuildFile();
    }

    if (!rootBuildInfo)
    {
        lmLog(compilerLogGroup, "Unable to open build info file '%s'", rootBuildFile.c_str());
        exit(EXIT_FAILURE);
    }

    if (rootBuildInfo->parseErrors)
    {
        lmLog(compilerLogGroup, "Please fix the following parser errors and recompile");
        LSCompilerLog::dump();
        exit(EXIT_FAILURE);
    }

    generateRootDependencies();

    compileRootBuildDependencies();
    compileAssembly(rootBuildInfo);
}
Exemple #2
0
// Dump connected clients to the console; useful for telling who is connected to the console!
static void listClients()
{
    loom_mutex_lock(gActiveSocketsMutex);

    // Blast it out to all clients.
    lmLog(gAssetAgentLogGroup, "Clients");

    for (UTsize i = 0; i < gActiveHandlers.size(); i++)
    {
        lmLog(gAssetAgentLogGroup, "   #%d - %s", gActiveHandlers[i]->getId(), gActiveHandlers[i]->description().c_str());
    }

    loom_mutex_unlock(gActiveSocketsMutex);
}
void platform_videoPlayFullscreen(const char *video, int scaleMode, int controlMode, unsigned int bgColor)
{
    ///strip out the raw filename only to use on Android
    int index = 0;
    int firstChar = 0;
    int lastChar = strlen(video) - 1;
    while(video[index] != '\0')
    {
        ///track extention start if found
        if(video[index] == '.')
        {
            lastChar = index - 1;
        }
        else if((video[index] == '/') || (video[index] == '\\'))
        {
            firstChar = index + 1;
        }
        index++;
    }
    int len = (lastChar - firstChar) + 1;
    char *newVideoName = new char[len + 1];
    memcpy(newVideoName, &video[firstChar], len * sizeof(char));
    newVideoName[len] = '\0';

    ///call java method to play the video
    lmLog(gAndroidVideoLogGroup, "videoPlayFullscreen: '%s' became '%s'", video, newVideoName);
    jstring jVideo    = gPlayVideoFullscreen.env->NewStringUTF(newVideoName);
    gPlayVideoFullscreen.env->CallStaticVoidMethod(gPlayVideoFullscreen.classID, gPlayVideoFullscreen.methodID, jVideo, scaleMode, controlMode, bgColor);
    gPlayVideoFullscreen.env->DeleteLocalRef(jVideo);
    delete []newVideoName;
}
void Java_co_theengine_loomdemo_LoomVideo_nativeCallback(JNIEnv *env, jobject thiz, jint callbackType, jstring data)
{
    lmLog(gAndroidVideoLogGroup, "LoomVideo Android Callback fired! %d", callbackType);

    const char *dataString = env->GetStringUTFChars(data, 0);
    if (gEventCallback)
    {
        const char *typeString = NULL;
        switch (callbackType)
        {
            case 0:
                gEventCallback("fail", dataString);
                break;

            case 1:
                lmLogError(gAndroidVideoLogGroup, "Video playback complete");
                gEventCallback("complete", dataString);
                break;

            default:
                lmLogError(gAndroidVideoLogGroup, "Got Android Video event of type %d but don't know how to handle it, ignoring...", callbackType);
                break;
        }
    }
    else
    {
        lmLogError(gAndroidVideoLogGroup, "Got Android Video event of type %d but don't know how to handle it, ignoring...", callbackType);
    }
    env->ReleaseStringUTFChars(data, dataString);
}
Exemple #5
0
void loom_asset_flush(const char *name)
{
   // Currently we only want to do this on the main thread so piggy back on the
   // native delegate sanity check to bail if on secondary thread.
   if(platform_getCurrentThreadId() != LS::NativeDelegate::smMainThreadID && LS::NativeDelegate::smMainThreadID != 0xBAADF00D)
      return;

   loom_mutex_lock(gAssetLock);
    // Delete it + unload it.
    loom_asset_t *asset = loom_asset_getAssetByName(name, 0);

   if(!asset || asset->isSupplied)
   {
      loom_mutex_unlock(gAssetLock);
      return;
   }
    
   lmLog(gAssetLogGroup, "Flushing '%s'", name);

    if (asset->blob)
    {
        asset->blob->decRef();
        asset->blob = NULL;
    }

    asset->state = loom_asset_t::Unloaded;

    // Fire subscribers.
    loom_asset_notifySubscribers(asset->name.c_str());

    loom_mutex_unlock(gAssetLock);
}
static bool getMethodInfo_(loomJniMethodInfo& methodinfo, const char *className, const char *methodName, const char *paramCode)
{
    jmethodID methodID = 0;
    JNIEnv    *pEnv    = 0;
    bool      bRet     = false;

    do
    {
        if (!getEnv(&pEnv))
        {
            break;
        }

        jclass classID = getClassID_(className, pEnv);

        if (!classID)
        {
            return false;
        }

        methodID = pEnv->GetMethodID(classID, methodName, paramCode);
        if (!methodID)
        {
            lmLog(jniLogGroup, "Failed to find method id of %s", methodName);
            break;
        }

        methodinfo.classID  = classID;
        methodinfo.methodID = methodID;

        bRet = true;
    } while (0);

    return bRet;
}
Exemple #7
0
void loom_asset_supply(const char *name, void *bits, int length)
{
    loom_mutex_lock(gAssetLock);

    // Prep the asset.
    loom_asset_t *asset = loom_asset_getAssetByName(name, 1);

    // Make sure it's pristine.
    lmAssert(asset->state == loom_asset_t::Unloaded, "Can't supply an asset that's already queued or in process of loading. Supply assets before you make any asset requests!");

    // Figure out the type from the path.
    utString nameAsUt = name;
    int      type     = loom_asset_recognizeAssetTypeFromPath(nameAsUt);

    if (type == 0)
    {
        lmLog(gAssetLogGroup, "Could not infer type of supplied resource '%s', skipping it...", name);
        asset->state = loom_asset_t::Unloaded;
        return;
    }

    // Deserialize it.
    LoomAssetCleanupCallback dtor = NULL;
    void *assetBits = loom_asset_deserializeAsset(name, type, length, bits, &dtor);

    // Instate the asset.
    // TODO: We can save some memory by pointing directly and not making a copy.
    asset->instate(type, assetBits, dtor);

    // Note it's supplied so we don't flush it.
    asset->isSupplied = 1;

    loom_mutex_unlock(gAssetLock);
}
Exemple #8
0
void loom_asset_initialize(const char *rootUri)
{
    // Set up the lock for the mutex.
    lmAssert(gAssetLock == NULL, "Double initialization!");
    gAssetLock = loom_mutex_create();

    // Note the CWD.
    char tmpBuff[1024];
    platform_getCurrentWorkingDir(tmpBuff, 1024);
    lmLog(gAssetLogGroup, "Current working directory ='%s'", tmpBuff);

    // And the allocator.
    //gAssetAllocator = loom_allocator_initializeTrackerProxyAllocator(loom_allocator_getGlobalHeap());
    gAssetAllocator = (loom_allocator_getGlobalHeap());

    // Clear, it might have been filled up before (for unit tests)
    gAssetLoadQueue.clear();
    gAssetHash.clear();

    // Asset server connection state.
    gAssetServerSocketLock = loom_mutex_create();

    // And set up some default asset types.
    loom_asset_registerType(LATText, loom_asset_textDeserializer, loom_asset_textRecognizer);
    loom_asset_registerType(LATBinary, loom_asset_binaryDeserializer, loom_asset_binaryRecognizer);

    loom_asset_registerImageAsset();
    loom_asset_registerSoundAsset();
    loom_asset_registerScriptAsset();

    // Listen to log and send it if we have a connection.
    loom_log_addListener(loom_asset_logListener, NULL);
}
const char *LoomJni::getPackageName()
{
    static utString packageName;

    if (packageName.size())
    {
        return packageName.c_str();
    }

    loomJniMethodInfo t;

    if (getStaticMethodInfo(t,
        "co/theengine/loomdemo/LoomDemo",
        "getActivityPackageName",
        "()Ljava/lang/String;"))
    {
        jstring str = (jstring)t.getEnv()->CallStaticObjectMethod(t.classID, t.methodID);
        t.getEnv()->DeleteLocalRef(t.classID);
        packageName = jstring2string(str);
        t.getEnv()->DeleteLocalRef(str);

        lmLog(jniLogGroup, "package name %s", packageName.c_str());

        return packageName.c_str();
    }

    return 0;
}
///initializes the data for the Mobile class for Android
void platform_mobileInitialize(SensorTripleChangedCallback sensorTripleChangedCB)
{
    lmLog(gAndroidMobileLogGroup, "INIT ***** MOBILE ***** ANDROID ****");

    gTripleChangedCallback = sensorTripleChangedCB;    


    ///Bind to JNI entry points.
    ///Mobile
    LoomJni::getStaticMethodInfo(gAllowScreenSleep,
                                 "co/theengine/loomdemo/LoomMobile",
                                 "allowScreenSleep",
                                 "(Z)V");
    LoomJni::getStaticMethodInfo(gIsSensorSupported,
                                 "co/theengine/loomdemo/LoomSensors",
                                 "isSensorSupported",
                                 "(I)Z");
    LoomJni::getStaticMethodInfo(gIsSensorEnabled,
                                 "co/theengine/loomdemo/LoomSensors",
                                 "isSensorEnabled",
                                 "(I)Z");
    LoomJni::getStaticMethodInfo(gHasSensorReceivedData,
                                 "co/theengine/loomdemo/LoomSensors",
                                 "hasSensorReceivedData",
                                 "(I)Z");
    LoomJni::getStaticMethodInfo(gEnableSensor,
                                 "co/theengine/loomdemo/LoomSensors",
                                 "enableSensor",
                                 "(I)Z");
    LoomJni::getStaticMethodInfo(gDisableSensor,
                                 "co/theengine/loomdemo/LoomSensors",
                                 "disableSensor",
                                 "(I)V");

    ///Dolby
    LoomJni::getStaticMethodInfo(gIsDolbyAudioSupported,
                                 "com/dolby/DolbyAudio",
                                 "isProcessingSupported",
                                 "()Z");
    LoomJni::getStaticMethodInfo(gSetDolbyAudioProcessingEnabled,
                                 "com/dolby/DolbyAudio",
                                 "setProcessingEnabled",
                                 "(Z)V");
    LoomJni::getStaticMethodInfo(gIsDolbyAudioProcessingEnabled,
                                 "com/dolby/DolbyAudio",
                                 "isProcessingEnabled",
                                 "()Z");
    LoomJni::getStaticMethodInfo(gIsDolbyAudioProcessingProfileSupported,
                                 "com/dolby/DolbyAudio",
                                 "isProcessingProfileSupported",
                                 "(Ljava/lang/String;)Z");
    LoomJni::getStaticMethodInfo(gSetDolbyAudioProcessingProfile,
                                 "com/dolby/DolbyAudio",
                                 "setProcessingProfile",
                                 "(Ljava/lang/String;)Z");
    LoomJni::getStaticMethodInfo(gGetSelectedDolbyAudioProfile,
                                 "com/dolby/DolbyAudio",
                                 "getSelectedProfile",
                                 "()Ljava/lang/String;");
}
static jclass getClassID_(const char *className, JNIEnv *env)
{
    JNIEnv *pEnv = env;
    jclass ret   = 0;

    do
    {
        if (!pEnv)
        {
            if (!getEnv(&pEnv))
            {
                break;
            }
        }

        ret = pEnv->FindClass(className);
        if (!ret)
        {
            lmLog(jniLogGroup, "Failed to find class of %s", className);

            jthrowable exc;
            exc = pEnv->ExceptionOccurred();
            if (exc)
            {
                pEnv->ExceptionClear();
            }
            break;
        }
    } while (0);

    return ret;
}
Exemple #12
0
void LSCompiler::logVerbose(const char *format, ...)
{
    char* buff;
    va_list args;
    lmLogArgs(args, buff, format);
    lmLog(compilerVerboseLogGroup, "%s", buff);
    lmFree(NULL, buff);
}
Exemple #13
0
void LSProfiler::dump(lua_State *L)
{
    dumpAllocations(L);

    while (methodStack.size())
    {
        leaveMethod(methodStack.peek(0)->getFullMemberName());
        methodStack.pop();
    }

    gLoomProfiler->dumpToConsole();

#ifdef LOOM_ENABLE_JIT
    lmLog(gProfilerLogGroup, "");
    lmLog(gProfilerLogGroup, "Please note: Profiling under JIT does not include native function calls.");
    lmLog(gProfilerLogGroup, "switch to the interpreted VM in order to gather native method timings");
#endif
}
Exemple #14
0
// Helper to fully shut down the listen socket; helps quite a bit on OS X.
static void shutdownListenSocket()
{
    if (gListenSocket)
    {
        lmLogDebug(gAssetAgentLogGroup, "Shutting down listen socket...");
        loom_net_closeTCPSocket(gListenSocket);
        lmLog(gAssetAgentLogGroup, "Done! Goodbye.");
        gListenSocket = 0;
    }
}
Exemple #15
0
/*
 * Get the implementation dependent name of a gamepad
 */
const char *
input_gamepadName(int device_index)
{
    if ((device_index < 0) || (device_index >= _numgamepads))
    {
        lmLog(gamepadLogGroup, "There are %d gamepads available", _numgamepads);
        return(NULL);
    }
    return(input_sysGamepadName(device_index));
}
Exemple #16
0
void loom_asset_pump()
{
   // Currently we only want to do this on the main thread so piggy back on the
   // native delegate sanity check to bail if on secondary thread.
   if(platform_getCurrentThreadId() != LS::NativeDelegate::smMainThreadID && LS::NativeDelegate::smMainThreadID != 0xBAADF00D)
      return;

   loom_mutex_lock(gAssetLock);

   // Talk to the asset server.
   loom_asset_serviceServer();

   // For now just blast all the data from each file into the asset.
   while(gAssetLoadQueue.size())
   {
      loom_asset_t *asset = gAssetLoadQueue.front();

      // Figure out the type from the path.
      utString path = asset->name;
      int type = loom_asset_recognizeAssetTypeFromPath(path);
      
      if(type == 0)
      {
         lmLog(gAssetLogGroup, "Could not infer type of resource '%s', skipping it...", path.c_str());
         asset->state = loom_asset_t::Unloaded;
         gAssetLoadQueue.erase((UTsize)0, true);
         continue;
      }

      // Open the file.
      void *ptr;
      long size;
      if(!platform_mapFile(asset->name.c_str(), &ptr, &size))
      {
         lmAssert(false, "Could not open file '%s'.", asset->name.c_str());
      }

      // Deserialize it.
      LoomAssetCleanupCallback dtor = NULL;
      void *assetBits = loom_asset_deserializeAsset(path, type, size, ptr, &dtor);

      // Close the file.
      platform_unmapFile(ptr);

      // Instate the asset.
      asset->instate(type, assetBits, dtor);

      // Done! Update queue.
      gAssetLoadQueue.erase((UTsize)0, true);
   }

   loom_mutex_unlock(gAssetLock);
}
void platform_videoInitialize(VideoEventCallback eventCallback)
{
    gEventCallback = eventCallback;

    lmLog(gAndroidVideoLogGroup, "INIT ***** VIDEO ***** ANDROID ****");

    // Bind to JNI entry points.
    LoomJni::getStaticMethodInfo(gPlayVideoFullscreen,
                                 "co/theengine/loomdemo/LoomVideo",
                                 "playFullscreen",
                                 "(Ljava/lang/String;III)V");
}
    static bool initBinderMethod(loomJniMethodInfo& methodInfo, const char *name, const char *sig)
    {
        bool result = LoomJni::getStaticMethodInfo(methodInfo,
                                                   "co/theengine/loomdemo/OuyaControllerBinder",
                                                   name,
                                                   sig);

        if (!result)
        {
            lmLog(ouyaLogGroup, "Error: unable to get method %s:%s", name, sig);
        }

        return result;
    }
Exemple #19
0
/*
 * Checks to make sure the gamepad is valid.
 */
int
input_privateGamepadValid(InputGamepad **gamepad)
{
    int valid;

    if (*gamepad == NULL)
    {
        lmLog(gamepadLogGroup, "Gamepad hasn't been opened yet");
        valid = 0;
    }
    else
    {
        valid = 1;
    }
    return valid;
}
Exemple #20
0
/*
 * Get the current state of an axis control on a gamepad
 */
int
input_gamepadGetAxis(InputGamepad *gamepad, int axis)
{
    int state;

    if (!input_privateGamepadValid(&gamepad))
    {
        return(0);
    }
    if (axis < gamepad->naxes)
    {
        state = gamepad->axes[axis];
    }
    else
    {
        lmLog(gamepadLogGroup, "Gamepad only has %d axes", gamepad->naxes);
        state = 0;
    }
    return(state);
}
Exemple #21
0
/*
 * Get the current state of a hat on a joystick
 */
int
input_gamepadGetHat(InputGamepad *gamepad, int hat)
{
    int state;

    if (!input_privateGamepadValid(&gamepad))
    {
        return(0);
    }
    if (hat < gamepad->nhats)
    {
        state = gamepad->hats[hat];
    }
    else
    {
        lmLog(gamepadLogGroup, "Gamepad only has %d hats", gamepad->nhats);
        state = 0;
    }
    return(state);
}
Exemple #22
0
/*
 * Get the current state of a button on a gamepad
 */
int
input_gamepadGetButton(InputGamepad *gamepad, int button)
{
    int state;

    if (!input_privateGamepadValid(&gamepad))
    {
        return(0);
    }
    if (button < gamepad->nbuttons)
    {
        state = gamepad->buttons[button];
    }
    else
    {
        lmLog(gamepadLogGroup, "Gamepad only has %d buttons", gamepad->nbuttons);
        state = 0;
    }
    return(state);
}
///initializes the data for the Parse class for Android
void platform_parseInitialize()
{
    lmLog(gAndroidParseLogGroup, "INIT ***** PARSE ***** ANDROID ****");

    ///Bind to JNI entry points.
    LoomJni::getStaticMethodInfo(gIsActive,
                                 "co/theengine/loomdemo/LoomParse",
                                 "isActive",
                                 "()Z");
    LoomJni::getStaticMethodInfo(gGetInstallationID,
                                 "co/theengine/loomdemo/LoomParse",
                                 "getInstallationID",
                                 "()Ljava/lang/String;");
    LoomJni::getStaticMethodInfo(gGetInstallationObjectID,
                                 "co/theengine/loomdemo/LoomParse",
                                 "getInstallationObjectID",
                                 "()Ljava/lang/String;");
    LoomJni::getStaticMethodInfo(gUpdateInstallationUserID,
                                 "co/theengine/loomdemo/LoomParse",
                                 "updateInstallationUserID",
                                 "(Ljava/lang/String;)Z");
}
const char *LoomJni::getWritablePath()
{
    static utString writablePath;

    loomJniMethodInfo t;

    if (getStaticMethodInfo(t,
        "co/theengine/loomdemo/LoomDemo",
        "getActivityWritablePath",
        "()Ljava/lang/String;"))
    {
        jstring str = (jstring)t.getEnv()->CallStaticObjectMethod(t.classID, t.methodID);
        writablePath = jstring2string(str);
        t.getEnv()->DeleteLocalRef(str);

        lmLog(jniLogGroup, "writable path %s", writablePath.c_str());

        return writablePath.c_str();
    }

    return 0;
}
Exemple #25
0
/**
 * Post all known files to all clients, or if specified, a single client.
 *
 * Useful for fully synching client with the current asset state.
 *
 * TODO: Optimize to use hashes to only transmit modified data, based on
 * client's starting assets.
 */
static void postAllFiles(int clientId = -1)
{
    lmLog(gAssetAgentLogGroup, "Queueing all files for client %d.", clientId);

    loom_mutex_lock(gFileScannerLock);

    // Walk all the files.
    utArray<FileEntry> *list = lmNew(NULL) utArray<FileEntry>();
    platform_walkFiles(".", handleFileStateWalkCallback, list);

    // Queue them all to be sent.
    for (UTsize i = 0; i < list->size(); i++)
    {
        FileModificationNote note;
        note.path          = stringtable_insert((*list)[i].path.c_str());
        note.lastSeenTime  = 0;
        note.onlyForClient = clientId;
        gPendingModifications.push_back(note);
    }

    loom_mutex_unlock(gFileScannerLock);
}
Exemple #26
0
const char *LoomJni::getSettingsPath()
{
    static utString path;

    loomJniMethodInfo t;

    if (getStaticMethodInfo(t,
        "co/theengine/loomplayer/LoomPlayer",
        "getActivitySettingsPath",
        "()Ljava/lang/String;"))
    {
        jstring str = (jstring)t.getEnv()->CallStaticObjectMethod(t.classID, t.methodID);
        path = jstring2string(str);
        t.getEnv()->DeleteLocalRef(str);

        lmLog(jniLogGroup, "settings path %s", path.c_str());

        return path.c_str();
    }

    return 0;
}
static int _jsonParseInt(const char *key, json_t *value)
{
    if (!value)
    {
        return 0;
    }

    if (json_is_true(value))
    {
        return 1;
    }

    if (json_is_false(value))
    {
        return 0;
    }

    if (json_is_integer(value))
    {
        return (int)json_integer_value(value);
    }

    if (json_is_real(value))
    {
        return (int)json_real_value(value);
    }

    if (json_is_string(value))
    {
        char *pEnd;
        return strtol(json_string_value(value), &pEnd, 10);
    }

    lmLog(gLoomApplicationConfigLogGroup, "WARNING: unknown json int conversion in config for key %s", key);

    return 0;
}
// little helpers that do conversion
static bool _jsonParseBool(const char *key, json_t *value)
{
    if (!value)
    {
        return false;
    }

    if (json_is_true(value))
    {
        return true;
    }

    if (json_is_false(value))
    {
        return false;
    }

    if (json_is_integer(value))
    {
        return json_integer_value(value) ? true : false;
    }

    if (json_is_real(value))
    {
        return json_real_value(value) ? true : false;
    }

    if (json_is_string(value))
    {
        return !stricmp(json_string_value(value), "true") ? true : false;
    }

    lmLog(gLoomApplicationConfigLogGroup, "WARNING: unknown json bool conversion in config for key %s", key);

    return false;
}
// this will always be in assets/loom.config (unless we decide to move it)
void LoomApplicationConfig::parseApplicationConfig(const utString& jsonString)
{
    configJSON = jsonString;

    // verify config is valid JSON

    json_error_t jerror;
    json_t       *json = json_loadb(jsonString.c_str(), jsonString.length(), 0, &jerror);

    lmAssert(json, "LoomApplicationConfig::parseApplicationConfig() error parsing application config %s\n %s %i\n", jerror.source, jerror.text, jerror.line);

    if (json_t *wfaa = json_object_get(json, "waitForAssetAgent"))
    {
        _waitForAssetAgent = _jsonParseInt("waitForAssetAgent", wfaa);
    }

    if (json_t *ah = json_object_get(json, "assetAgentHost"))
    {
        if (!json_is_string(ah))
        {
            lmLog(gLoomApplicationConfigLogGroup, "assetAgentHost was specified but is not a string!");
        }
        else
        {
            assetHost = json_string_value(ah);
        }
    }

    if (json_t *ap = json_object_get(json, "assetAgentPort"))
    {
        assetPort = _jsonParseInt("assetAgentPort", ap);
    }

    const char *v = json_string_value(json_object_get(json, "version"));
    if (v != NULL)
    {
        _version = v;
    }

    const char *app_id = json_string_value(json_object_get(json, "app_id"));
    if (app_id != NULL)
    {
        _applicationId = app_id;
    }

    // Parse log block.
    if (json_t *logBlock = json_object_get(json, "log"))
    {
        // Walk the children.
        const char *key;
        json_t     *value;

        json_object_foreach(logBlock, key, value)
        {
            // Key is the prefix for the rule.
            // Maybe we have level or enabled data?
            int enabledRule = -1;
            int filterRule  = -1;

            if (json_t *enabledBlock = json_object_get(value, "enabled"))
            {
                enabledRule = _jsonParseBool("log.enabled", value);
            }

            // TODO: Allow info, warn, error as parameters here.
            if (json_t *levelBlock = json_object_get(value, "level"))
            {
                filterRule = (int)json_integer_value(levelBlock);
            }

            loom_log_addRule(key, enabledRule, filterRule);
        }
    }
Exemple #30
0
// Service our connection to the asset agent.
static void loom_asset_serviceServer()
{
    loom_mutex_lock(gAssetServerSocketLock);

    // Try to connect to the asset server if we aren't already, and it is set.
    if ((gAssetServerSocket == NULL) &&
        ((ASSET_STREAM_HOST != NULL) && (strlen(ASSET_STREAM_HOST) > 0)) &&
        ((platform_getMilliseconds() - gAssetServerLastConnectTryTime) > gAssetServerConnectTryInterval))
    {
        lmLog(gAssetLogGroup, "Attempting to stream assets from %s:%d", ASSET_STREAM_HOST, ASSET_STREAM_PORT);
        gAssetServerLastConnectTryTime = platform_getMilliseconds();
        gAssetServerSocket             = loom_net_openTCPSocket(ASSET_STREAM_HOST, ASSET_STREAM_PORT, 0);
        gAssetConnectionOpen           = false;
        loom_asset_notifyPendingCountChange();

        loom_mutex_unlock(gAssetServerSocketLock);
        return;
    }

    if ((gAssetServerSocket != NULL) && (gAssetConnectionOpen == false))
    {
        // We are waiting on the connection, see if it's writable... If not, return.
        if (loom_net_isSocketWritable(gAssetServerSocket) == 0)
        {
            loom_mutex_unlock(gAssetServerSocketLock);
            return;
        }

        if (loom_net_isSocketDead(gAssetServerSocket) == 1)
        {
            // Might be DOA, ie, connect failed.
            lmLog(gAssetLogGroup, "Failed to connect to asset server %s:%d", ASSET_STREAM_HOST, ASSET_STREAM_PORT);

            loom_net_closeTCPSocket(gAssetServerSocket);

            gAssetServerSocket = NULL;
            lmSafeDelete(NULL, gAssetProtocolHandler);
            gAssetConnectionOpen = false;
            loom_asset_notifyPendingCountChange();
            loom_mutex_unlock(gAssetServerSocketLock);
            return;
        }

        lmLog(gAssetLogGroup, "Successfully connected to asset server %s:%d!", ASSET_STREAM_HOST, ASSET_STREAM_PORT);

        // Do this now to avoid clobbering error state and seeing the socket as
        // "open" when it is really dead.
        loom_net_enableSocketKeepalive(gAssetServerSocket);
        gAssetConnectionOpen = true;
        loom_asset_notifyPendingCountChange();

        // Make sure we have a protocol handler.
        if (!gAssetProtocolHandler)
        {
            gAssetProtocolHandler = lmNew(NULL) AssetProtocolHandler(gAssetServerSocket);
            gAssetProtocolHandler->registerListener(lmNew(NULL) AssetProtocolFileMessageListener());
            gAssetProtocolHandler->registerListener(lmNew(NULL) AssetProtocolCommandListener());
        }

        loom_mutex_unlock(gAssetServerSocketLock);
        return;
    }

    // See if the socket is dead, and if so, clean up.
    if ((gAssetServerSocket != NULL) && (loom_net_isSocketDead(gAssetServerSocket) == 1))
    {
        lmLog(gAssetLogGroup, "Lost connection to asset server.");
        loom_net_closeTCPSocket(gAssetServerSocket);
        gAssetServerSocket = NULL;
        lmSafeDelete(NULL, gAssetProtocolHandler);
        gAssetConnectionOpen = false;
        loom_asset_notifyPendingCountChange();
        loom_mutex_unlock(gAssetServerSocketLock);
        return;
    }

    // Bail if we don't have a connection.
    if (!gAssetServerSocket || !gAssetConnectionOpen)
    {
        loom_mutex_unlock(gAssetServerSocketLock);
        return;
    }

    // Ping if we need to.
    if (platform_getMilliseconds() - gAssetServerLastPingTime > gAssetServerPingInterval)
    {
        gAssetProtocolHandler->sendPing();
        gAssetServerLastPingTime = platform_getMilliseconds();
    }

    // Service the asset server connection.
    gAssetProtocolHandler->process();

    loom_mutex_unlock(gAssetServerSocketLock);
}