Beispiel #1
0
void loom_asset_preload(const char *name)
{
    loom_mutex_lock(gAssetLock);

    // Look 'er up.
    loom_asset_t *asset = loom_asset_getAssetByName(name, 1);

    // If it's not pending load, then stick it in the queue.
    if (loom_asset_isOnTrackToLoad(asset))
    {
        loom_mutex_unlock(gAssetLock);
        return;
    }

    asset->state = loom_asset_t::QueuedForDownload;
    gAssetLoadQueue.push_back(asset);

    loom_mutex_unlock(gAssetLock);
}
Beispiel #2
0
// Helper function to route Loom log output over the network.
void loom_asset_logListener(void *payload, loom_logGroup_t *group, loom_logLevel_t level, const char *msg)
{
    loom_mutex_lock(gAssetServerSocketLock);

    if (gAssetProtocolHandler)
    {
        gAssetProtocolHandler->sendLog(msg);
    }

    loom_mutex_unlock(gAssetServerSocketLock);
}
Beispiel #3
0
// Helper function to route Loom custom output over the network.
void loom_asset_custom(void* buffer, int length)
{
    loom_mutex_lock(gAssetServerSocketLock);

    if (gAssetProtocolHandler)
    {
        gAssetProtocolHandler->sendCustom(buffer, length);
    }

    loom_mutex_unlock(gAssetServerSocketLock);
}
Beispiel #4
0
static void enqueueFileChangeCallback(const char *path)
{
    CallbackQueueNote *cqn = lmNew(NULL) CallbackQueueNote();

    cqn->type = QNT_Change;
    cqn->text = utString(path);

    loom_mutex_lock(gCallbackLock);
    gCallbackQueue.push_back(cqn);
    loom_mutex_unlock(gCallbackLock);
}
Beispiel #5
0
static void enqueueLogCallback(const char *msg)
{
    CallbackQueueNote *cqn = lmNew(NULL) CallbackQueueNote();

    cqn->type = QNT_Log;
    cqn->text = utString(msg);

    loom_mutex_lock(gCallbackLock);
    gCallbackQueue.push_back(cqn);
    loom_mutex_unlock(gCallbackLock);
}
Beispiel #6
0
void loom_asset_notifySubscribers(const char *name)
{
    loom_mutex_lock(gAssetLock);

    loom_asset_t *asset = loom_asset_getAssetByName(name, 0);

    if (!asset)
    {
        loom_mutex_unlock(gAssetLock);
        return;
    }

    for (UTsize i = 0; i < asset->subscribers.size(); i++)
    {
        loom_asset_subscription_t& s = asset->subscribers[i];
        s.callback(s.payload, name);
    }

    loom_mutex_unlock(gAssetLock);
}
Beispiel #7
0
void loom_asset_reload(const char *name)
{
    loom_mutex_lock(gAssetLock);

    loom_asset_t *asset = loom_asset_getAssetByName(name, 1);

    // Put it in the queue, this will trigger a new blob to be loaded.
    gAssetLoadQueue.push_back(asset);

    loom_mutex_unlock(gAssetLock);
}
Beispiel #8
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 NativeDelegate::postNativeDelegateCallNote(NativeDelegateCallNote *ndcn)
{
    ensureQueueInit();
    loom_mutex_lock(gCallNoteMutex);

    // Prep for reading.
    ndcn->rewind();

    // Store for later access.
    gNDCallNoteQueue.push_back(ndcn);

    loom_mutex_unlock(gCallNoteMutex);
}
Beispiel #10
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);
}
Beispiel #11
0
static void postResampleEvent(const char *path, float progress)
{
    if (gEventQueueMutex == NULL)
    {
        gEventQueueMutex = loom_mutex_create();
    }

    loom_mutex_lock(gEventQueueMutex);

    RescaleEventStatus res;
    res.path     = path;
    res.progress = progress;
    gEventQueue.push_back(res);

    loom_mutex_unlock(gEventQueueMutex);
}
Beispiel #12
0
float loom_asset_checkLoadedPercentage(const char *name)
{
    loom_mutex_lock(gAssetLock);

    // Look it up.
    loom_asset_t *asset = loom_asset_getAssetByName(name, 0);

    loom_mutex_unlock(gAssetLock);

    if (!asset)
    {
        return 0.f;
    }

    // If loaded, return 1, else 0. (For now.)
    return asset->state == loom_asset_t::Loaded ? 1.f : 0.2f;
}
Beispiel #13
0
int loom_asset_pending(const char *name)
{
    loom_mutex_lock(gAssetLock);
    
    // Look 'er up.
    loom_asset_t *asset = loom_asset_getAssetByName(name, 0);
    
    // If it's not pending load, then stick it in the queue.
    int result;
    if(asset && loom_asset_isOnTrackToLoad(asset))
        result = 1;
    else
        result = 0;
    
    loom_mutex_unlock(gAssetLock);
    
    return result;
}
Beispiel #14
0
static CallbackQueueNote *dequeueCallback()
{
    CallbackQueueNote *cqn = NULL;

    loom_mutex_lock(gCallbackLock);

    if (gCallbackQueue.begin() == NULL)
    {
        cqn = NULL;
    }
    else
    {
        cqn = gCallbackQueue.front();
        gCallbackQueue.pop_front();
    }

    loom_mutex_unlock(gCallbackLock);

    return cqn;
}
Beispiel #15
0
static loom_asset_t *loom_asset_getAssetByName(const char *name, int create)
{
    loom_mutex_lock(gAssetLock);
    static char normalized[4096];
    strncpy(normalized, name, sizeof(normalized));
    platform_normalizePath(normalized);
    utHashedString key        = normalized;
    loom_mutex_unlock(gAssetLock);
    loom_asset_t   **assetPtr = gAssetHash.get(key);
    loom_asset_t   *asset     = assetPtr ? *assetPtr : NULL;

    if ((asset == NULL) && create)
    {
        // Create one.
        asset       = lmNew(gAssetAllocator) loom_asset_t;
        asset->name = name;
        gAssetHash.insert(key, asset);
    }

    return asset;
}
Beispiel #16
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);
}
Beispiel #17
0
void loom_asset_unlock( const char *name )
{
   // Hack to report usage.
   //size_t allocBytes, allocCount;
   //loom_allocator_getTrackerProxyStats(gAssetAllocator, &allocBytes, &allocCount);
   //lmLogError(gAssetLogGroup, "Seeing %d bytes of allocator and %d allocations", allocBytes, allocCount);

   loom_mutex_lock(gAssetLock);

   // TODO: This needs to be against the blob we locked NOT the asset's
   //       current state.

   // Look it up.
   loom_asset_t *asset = loom_asset_getAssetByName(name, 0);

   // Assert if not loaded.
   lmAssert(asset, "Could not find asset '%s' to unlock!", name);
   //lmAssert(asset->blob, "Asset was not locked!");

   if(asset->state == loom_asset_t::Loaded)
   {
      // Dec count.
      if(asset->blob->decRef())
      {
         asset->state = loom_asset_t::Unloaded;
         asset->blob = NULL;
      }
   }
   else
   {
      // Nothing - it's not loaded.
      lmLogWarn(gAssetLogGroup, "Couldn't unlock '%s' as it was not loaded.", name);
   }

   loom_mutex_unlock(gAssetLock);
}
Beispiel #18
0
void NativeDelegate::executeDeferredCalls(lua_State *L)
{
    ensureQueueInit();
    loom_mutex_lock(gCallNoteMutex);

    // Try to resolve the delegate pointer.
    utArray<NativeDelegate *> *delegates = NULL;
    if (sActiveNativeDelegates.find(L) != UT_NPOS)
    {
        delegates = *(sActiveNativeDelegates.get(L));
    }
    else
    {
        // No delegate list, can't do it.
        loom_mutex_unlock(gCallNoteMutex);
        return;
    }

    for(unsigned int i=0; i<gNDCallNoteQueue.size(); i++)
    {
        NativeDelegateCallNote *ndcn = gNDCallNoteQueue[i];

        bool found = false;
        for(unsigned int i=0; i<delegates->size(); i++)
        {
            // Look for our delegate.
            if((*delegates)[i] != ndcn->delegate)
                continue;

            // If key mismatches, warn and bail.
            if((*delegates)[i]->_key != ndcn->delegateKey)
            {
                lmLogError(gNativeDelegateGroup, "Found delegate call note with key mismatch (delegate=%x actualKey=%x expectedKey=%x), ignoring...", (*delegates)[i], (*delegates)[i]->_key, ndcn->delegateKey);
                break;
            }

            // Match!
            found = true;
            break;
        }

        // Bail if no match.
        if(!found)
            continue;

        // Otherwise, let's call it.
        const NativeDelegate *theDelegate = ndcn->delegate;
        for(;;)
        {
            unsigned char actionType = ndcn->readByte();
            bool done = false;
            char *str = NULL;
            utByteArray *bytes;
            switch(actionType)
            {
                case MSG_Nop:
                    lmLogError(gNativeDelegateGroup, "Got a nop in delegate data stream.");
                break;

                case MSG_PushString:
                    str = ndcn->readString();
                    theDelegate->pushArgument(str);
                    free(str);
                    break;

                case MSG_PushByteArray:
                    bytes = ndcn->readByteArray();
                    theDelegate->pushArgument(bytes);
                    free(bytes);
                    break;

                case MSG_PushDouble:
                    theDelegate->pushArgument(ndcn->readDouble());
                    break;

                case MSG_PushFloat:
                    theDelegate->pushArgument(ndcn->readFloat());
                    break;
                
                case MSG_PushInt:
                    theDelegate->pushArgument((int)ndcn->readInt());
                    break;
                
                case MSG_PushBool:
                    theDelegate->pushArgument(ndcn->readBool());
                    break;

                case MSG_Invoke:
                    theDelegate->invoke();
                    done = true;
                    break;
            }

            if(done)
                break;
        }

    }

    // Purge queue.
    gNDCallNoteQueue.clear();

    loom_mutex_unlock(gCallNoteMutex);
}
Beispiel #19
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);
}
Beispiel #20
0
// Take a difference report from compareFileEntries and issue appropriate
// file modification notes, and check whether they have settled. If so,
// transmit updates to clients.
static void processFileEntryDeltas(utArray<FileEntryDelta> *deltas)
{
    int curTime = platform_getMilliseconds();

    loom_mutex_lock(gFileScannerLock);

    // Update the pending list with all the stuff we've seen.
    for (UTsize i = 0; i < deltas->size(); i++)
    {
        // Get the delta.
        const FileEntryDelta& fed = deltas->at(i);

        // If it's removal, we don't currently send a notification.
        if (fed.action == FileEntryDelta::Removed)
        {
            continue;
        }

        // If it's not whitelisted, ignore it.
        if (!checkInWhitelist(fed.path))
        {
            continue;
        }

        // Note it in the pending modification list.
        bool sawInList = false;
        for (UTsize i = 0; i < gPendingModifications.size(); i++)
        {
            FileModificationNote& fmn = gPendingModifications.at(i);
            if (strcmp(fmn.path, fed.path.c_str()))
            {
                continue;
            }

            // Match - update time.
            lmLogDebug(gAssetAgentLogGroup, "FILE CHANGING - '%s'", fed.path.c_str());
            fmn.lastSeenTime = curTime;
            sawInList        = true;
        }

        if (!sawInList)
        {
            FileModificationNote fmn;
            fmn.path         = stringtable_insert(fed.path.c_str());
            fmn.lastSeenTime = curTime;
            gPendingModifications.push_back(fmn);
            lmLogDebug(gAssetAgentLogGroup, "FILE CHANGED  - '%s'", fed.path.c_str());
        }
    }

    // Now, walk the pending list and send everyone who hasn't been touched for the settling period.

    // See how many files we're sending and note that state.
    const int settleTimeMs = 750;

    int transferStartTime     = platform_getMilliseconds();
    int totalPendingTransfers = 0;
    for (UTsize i = 0; i < gPendingModifications.size(); i++)
    {
        // Only consider pending items that have aged out.
        FileModificationNote& fmn = gPendingModifications.at(i);
        if (curTime - fmn.lastSeenTime < settleTimeMs)
        {
            continue;
        }

        totalPendingTransfers++;
    }

    bool didWeNotifyUserAboutPending = false;

    for (UTsize i = 0; i < gPendingModifications.size(); i++)
    {
        // Only consider pending items that have aged out.
        FileModificationNote& fmn = gPendingModifications.at(i);
        if (curTime - fmn.lastSeenTime < settleTimeMs)
        {
            continue;
        }

        // Make the path canonical.
        utString filename = fmn.path;
        char     canonicalFile[MAXPATHLEN];
        makeAssetPathCanonical(filename.c_str(), canonicalFile);

        // Note: we don't deal with deleted files properly (by uploading new state) because realpath
        // only works right when the file exists. So we just skip doing anything about it.
        // Note we are using gActiveHandlers.size() outside of a lock, but this is ok as it's a word.
        if ((strstr(canonicalFile, ".loom") || strstr(canonicalFile, ".ls")) && (gActiveHandlers.size() > 0))
        {
            lmLog(gAssetAgentLogGroup, "Changed '%s'", canonicalFile);
        }

        if (canonicalFile[0] == 0)
        {
            lmLog(gAssetAgentLogGroup, "   o Ignoring file missing from the asset folder!");

            // Remove from the pending list.
            gPendingModifications.erase(i);
            i--;

            continue;
        }

        // Queue the callback.
        enqueueFileChangeCallback(canonicalFile);

        // Map the file.
        void *fileBits      = NULL;
        long fileBitsLength = 0;
        if (!platform_mapFile(canonicalFile, &fileBits, &fileBitsLength))
        {
            lmLog(gAssetAgentLogGroup, "   o Skipping due to file failing to map.");
            continue;
        }

        // Loop over the active sockets.
        loom_mutex_lock(gActiveSocketsMutex);

        // Blast it out to all clients.
        for (UTsize j = 0; j < gActiveHandlers.size(); j++)
        {
            // If it's for a specific client then only send to that client.
            if ((fmn.onlyForClient != -1) && (fmn.onlyForClient != gActiveHandlers[j]->getId()))
            {
                continue;
            }

            gActiveHandlers[j]->sendFile(canonicalFile, fileBits, fileBitsLength, totalPendingTransfers);

            // If it has been more than a second, note that we are still working.
            const int remainingTransferCount = (totalPendingTransfers * gActiveHandlers.size()) - j;
            if (((platform_getMilliseconds() - transferStartTime) > 2000) && (remainingTransferCount > 1))
            {
                transferStartTime = platform_getMilliseconds();
                lmLog(gAssetAgentLogGroup, "Still transferring files. %d to go!", remainingTransferCount - 1);
                didWeNotifyUserAboutPending = true;
            }
        }

        loom_mutex_unlock(gActiveSocketsMutex);

        totalPendingTransfers--;

        // Unmap the file.
        platform_unmapFile(fileBits);

        // Remove from the pending list.
        gPendingModifications.erase(i);
        i--;
    }

    loom_mutex_unlock(gFileScannerLock);

    if (didWeNotifyUserAboutPending)
    {
        lmLog(gAssetAgentLogGroup, "Done transferring files!");
    }
}
Beispiel #21
0
StringTableEntry stringtable_insert(const char *str)
{
    unsigned long      hash_result;
    stringTableEntry_t *walk  = NULL;
    StringTableEntry   result = NULL;
    int                bucket;

    // Hash the string.
    hash_result = hash(str);

    // Determine the hash table bucket.
    bucket = hash_result % csmTableSize;

    // TODO: Make this a readwrite lock, since most of the time we are just
    // traversing the stringtable to find existing data, not inserting.
    loom_mutex_lock(gTableMutex);

    // Walk the chain, if any.
    walk = gTable[bucket];

    // Empty bucket, easy case!
    if (!walk)
    {
        gTable[bucket] = allocEntry(str);
        assert(gTable[bucket]->string);
        result = gTable[bucket]->string;
        loom_mutex_unlock(gTableMutex);

        return result;
    }

    while (walk)
    {
        // Is it a match?
        if (strcmp(walk->string, str) == 0)
        {
            assert(walk->string);
            result = (StringTableEntry)walk->string;
            loom_mutex_unlock(gTableMutex);

            return result;
        }

        // Another one to check?
        if (walk->next != NULL)
        {
            walk = walk->next;
            continue;
        }

        break;
    }

    // No match. Chain 'er on.
    walk->next = allocEntry(str);
    assert(walk->next->string);
    result = walk->next->string;
    loom_mutex_unlock(gTableMutex);

    return result;
}
Beispiel #22
0
// Entry point for the socket thread. Listen for connections and incoming data,
// and route it to the protocol handlers.
static int socketListeningThread(void *payload)
{
    // Listen for incoming connections.
    int listenPort = 12340;

    gListenSocket = (loom_socketId_t)-1;
    for ( ; ; )
    {
        gListenSocket = loom_net_listenTCPSocket(listenPort);

        if (gListenSocket != (loom_socketId_t)-1)
        {
            break;
        }

        lmLogWarn(gAssetAgentLogGroup, "   - Failed to acquire port %d, trying port %d", listenPort, listenPort + 1);
        listenPort++;
    }

    lmLog(gAssetAgentLogGroup, "Listening on port %d", listenPort);

    while (loom_socketId_t acceptedSocket = loom_net_acceptTCPSocket(gListenSocket))
    {
        // Check to see if we got anybody...
        if (!acceptedSocket || ((int)(long)acceptedSocket == -1))
        {
            // Process the connections.
            loom_mutex_lock(gActiveSocketsMutex);
            
            for (UTsize i = 0; i < gActiveHandlers.size(); i++)
            {
                AssetProtocolHandler* aph = gActiveHandlers[i];
                aph->process();

                // Check for ping timeout
                int msSincePing = loom_readTimer(aph->lastActiveTime);
                if (msSincePing > socketPingTimeoutMs)
                {
                    gActiveHandlers.erase(i);
                    i--;
                    lmLog(gAssetAgentLogGroup, "Client timed out (%x)", aph->socket);
                    loom_net_closeTCPSocket(aph->socket);
                    lmDelete(NULL, aph);
                }
            }

            loom_mutex_unlock(gActiveSocketsMutex);

            loom_thread_sleep(10);
            continue;
        }

        lmLog(gAssetAgentLogGroup, "Client connected (%x)", acceptedSocket);

        loom_mutex_lock(gActiveSocketsMutex);
        gActiveHandlers.push_back(lmNew(NULL) AssetProtocolHandler(acceptedSocket));

        AssetProtocolHandler *handler = gActiveHandlers.back();
        handler->registerListener(lmNew(NULL) TelemetryListener());
        if (TelemetryServer::isRunning()) handler->sendCommand("telemetryEnable");

        // Send it all of our files.
        // postAllFiles(gActiveHandlers[gActiveHandlers.size()-1]->getId());

        loom_mutex_unlock(gActiveSocketsMutex);
    }

    return 0;
}