Implement fallback logic from HTTP downloading to UDP.

This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.

HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.

If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.

So: On the HTTP side it's tracked if there was an error or not.  After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
This commit is contained in:
Yamagi Burmeister 2019-01-16 13:00:43 +01:00
parent 78366472f9
commit eb048e8611
3 changed files with 70 additions and 14 deletions

View file

@ -39,6 +39,9 @@ extern int precache_model_skin;
extern byte *precache_model; extern byte *precache_model;
// Forces all downloads to UDP.
qboolean forceudp = false;
static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
#define PLAYER_MULT 5 #define PLAYER_MULT 5
@ -341,19 +344,32 @@ CL_RequestNextDownload(void)
} }
} }
} }
/* precache phase completed */
precache_check = ENV_CNT;
} }
#ifdef USE_CURL #ifdef USE_CURL
/* Wait for pending downloads. */ /* Wait for pending downloads. */
if (CL_PendingHTTPDownloads()) if (CL_PendingHTTPDownloads())
{ {
return; return;
} }
if (CL_CheckHTTPError())
{
/* Mkay, there were download errors. Let's start over,
this time with UDP. This will reuse all files that
succeeded over HTTP. */
forceudp = true;
CL_ResetPrecacheCheck();
CL_RequestNextDownload();
return;
}
#endif #endif
/* precache phase completed */
precache_check = ENV_CNT;
if (precache_check == ENV_CNT) if (precache_check == ENV_CNT)
{ {
precache_check = ENV_CNT + 1; precache_check = ENV_CNT + 1;
@ -436,6 +452,9 @@ CL_RequestNextDownload(void)
} }
#endif #endif
/* This map was done, allow HTTP again for next map. */
forceudp = false;
CL_RegisterSounds(); CL_RegisterSounds();
CL_PrepRefresh(); CL_PrepRefresh();
@ -488,17 +507,31 @@ CL_CheckOrDownloadFile(char *filename)
} }
#ifdef USE_CURL #ifdef USE_CURL
if (CL_QueueHTTPDownload(filename)) if (!forceudp)
{ {
/* We return true so that the precache check if (CL_QueueHTTPDownload(filename))
keeps feeding us more files. Since we have {
multiple HTTP connections we want to /* We return true so that the precache check
minimize latency and be constantly sending keeps feeding us more files. Since we have
requests, not one at a time. */ multiple HTTP connections we want to
return true; minimize latency and be constantly sending
requests, not one at a time. */
return true;
}
}
else
{
/* There're 2 cases:
- forceudp was set after a 404. In this case we
want to retry that single file over UDP and
all later files over HTTP.
- forceudp was set after another error code.
In that case the HTTP code aborts all HTTP
downloads and CL_QueueHTTPDownload() returns
false. */
forceudp = false;
} }
#endif #endif
strcpy(cls.downloadname, filename); strcpy(cls.downloadname, filename);
/* download to a temp name, and only rename /* download to a temp name, and only rename

View file

@ -48,6 +48,7 @@ static int handleCount = 0;
static int pendingCount = 0; static int pendingCount = 0;
static int abortDownloads = HTTPDL_ABORT_NONE; static int abortDownloads = HTTPDL_ABORT_NONE;
static qboolean httpDown = false; static qboolean httpDown = false;
static qboolean downloadError = false;
// -------- // --------
@ -582,18 +583,23 @@ static void CL_FinishHTTPDownload(void)
{ {
Com_Printf("HTTP download: %s - File Not Found\n", dl->queueEntry->quakePath); Com_Printf("HTTP download: %s - File Not Found\n", dl->queueEntry->quakePath);
// We got a 404, remove the target file. // We got a 404, remove the target file...
if (isFile) if (isFile)
{ {
Sys_Remove(dl->filePath); Sys_Remove(dl->filePath);
isFile = false;
} }
// ...and remove it from the CURL multihandle. // ...remove it from the CURL multihandle...
qcurl_multi_remove_handle(multi, dl->curl); qcurl_multi_remove_handle(multi, dl->curl);
CL_RemoveFromQueue(dl->queueEntry); CL_RemoveFromQueue(dl->queueEntry);
dl->queueEntry = NULL; dl->queueEntry = NULL;
// ...and communicate the error.
if (isFile)
{
downloadError = true;
isFile = false;
}
break; break;
@ -1050,6 +1056,22 @@ qboolean CL_PendingHTTPDownloads(void)
return pendingCount + handleCount; return pendingCount + handleCount;
} }
/* Checks if there was an error. Returns
* true if yes, and false if not.
*/
qboolean CL_CheckHTTPError()
{
if (downloadError)
{
downloadError = false;
return true;
}
else
{
return false;
}
}
/* /*
* Calls CURL to perform the actual downloads. * Calls CURL to perform the actual downloads.
* Must be called every frame, otherwise CURL * Must be called every frame, otherwise CURL

View file

@ -72,6 +72,7 @@ void CL_RunHTTPDownloads(void);
qboolean CL_PendingHTTPDownloads(void); qboolean CL_PendingHTTPDownloads(void);
void CL_SetHTTPServer(const char *URL); void CL_SetHTTPServer(const char *URL);
void CL_HTTP_Cleanup(qboolean fullShutdown); void CL_HTTP_Cleanup(qboolean fullShutdown);
qboolean CL_CheckHTTPError();
#endif // DOWNLOAD_H #endif // DOWNLOAD_H
#endif // USE_CURL #endif // USE_CURL