Begin working on Emscripten port

- SDL_Mixer has some problems, so sound is disabled
- stderr replaced with stdout so the Emscripten page shows some output
This commit is contained in:
Justin Jacobs
2017-07-12 11:34:25 -04:00
parent af3f6537c2
commit b20623ba3e
15 changed files with 251 additions and 72 deletions

1
.gitignore vendored
View File

@@ -105,3 +105,4 @@ Thumbs.db
src/cdogs/include/
src/cdogs/share/
*.blend1
emscripten/

32
make_emscripten.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
rm -rf emscripten/*
mkdir -p emscripten
cmake .
emcc -D "PB_FIELD_16BIT=1" \
-Isrc/ \
-Isrc/cdogs/ \
-Isrc/cdogs/proto/nanopb/ \
-Isrc/cdogs/enet/include/ \
-Isrc/cdogs/include/ \
-Isrc/tests/ \
src/*.c \
$(find src/cdogs/ -name "*.c") \
src/json/*.c \
-O3 \
-s ASSERTIONS=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s USE_SDL=2 \
-s USE_SDL_IMAGE=2 \
-s SDL2_IMAGE_FORMATS='["png"]' \
--preload-file data \
--preload-file doc \
--preload-file dogfights \
--preload-file graphics \
--preload-file missions \
--preload-file music \
--preload-file sounds \
-o emscripten/index.html

View File

@@ -485,6 +485,7 @@ struct option longopts[] =
{ 0, 0, 0, 0 }
};
#ifndef __EMSCRIPTEN__
int
main(int argc, char **argv)
{
@@ -512,5 +513,6 @@ main(int argc, char **argv)
printf("flag = '%c'\n", heckle);
return 0;
}
#endif //__EMSCRIPTEN__
#endif

View File

@@ -128,6 +128,7 @@ int main(int argc, char *argv[])
AutosaveInit(&gAutosave);
AutosaveLoad(&gAutosave, GetConfigFilePath(AUTOSAVE_FILE));
#ifndef __EMSCRIPTEN__
if (enet_initialize() != 0)
{
LOG(LM_MAIN, LL_ERROR, "An error occurred while initializing ENet.");
@@ -135,6 +136,7 @@ int main(int argc, char *argv[])
goto bail;
}
NetClientInit(&gNetClient);
#endif
// Print command line
char buf[CDOGS_PATH_MAX];
@@ -146,9 +148,13 @@ int main(int argc, char *argv[])
}
debug(D_NORMAL, "Initialising SDL...\n");
#ifndef __EMSCRIPTEN__
const int sdlFlags =
SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_HAPTIC |
SDL_INIT_GAMECONTROLLER;
#else
const int sdlFlags = SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
#endif
if (SDL_Init(sdlFlags) != 0)
{
LOG(LM_MAIN, LL_ERROR, "Could not initialise SDL: %s", SDL_GetError());
@@ -168,6 +174,7 @@ int main(int argc, char *argv[])
LOG(LM_MAIN, LL_INFO, "data dir(%s)", buf);
LOG(LM_MAIN, LL_INFO, "config dir(%s)", GetConfigFilePath(""));
#ifndef __EMSCRIPTEN__
SoundInitialize(&gSoundDevice, "sounds");
if (!gSoundDevice.isInitialised)
{
@@ -177,6 +184,7 @@ int main(int argc, char *argv[])
LoadSongs();
MusicPlayMenu(&gSoundDevice);
#endif
EventInit(&gEventHandlers, NULL, NULL, true);
NetServerInit(&gNetServer);

View File

@@ -1,6 +1,7 @@
/*
* A unit test and example of how to use the simple C hashmap
*/
#ifndef __EMSCRIPTEN__
#include <stdlib.h>
#include <stdio.h>
@@ -78,4 +79,5 @@ int main(char* argv, int argc)
hashmap_free(mymap);
return 1;
}
}
#endif //__EMSCRIPTEN__

View File

@@ -54,6 +54,10 @@
#include <sys/poll.h>
#endif
#ifdef __EMSCRIPTEN__
#define HAS_SOCKLEN_T 1
#endif
#ifndef HAS_SOCKLEN_T
typedef int socklen_t;
#endif

View File

@@ -55,7 +55,11 @@
#include <ctype.h>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <SDL/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include <tinydir/tinydir.h>
#include "actors.h"

View File

@@ -31,6 +31,10 @@
#include "sys_specifics.h"
#ifdef __EMSCRIPTEN__
#define stderr stdout
#endif
typedef enum
{

View File

@@ -31,7 +31,11 @@
#include <string.h>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <SDL/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include "config.h"
#include "gamedata.h"

View File

@@ -82,6 +82,7 @@ int OpenAudio(int frequency, Uint16 format, int channels, int chunkSize)
}
// Check that we got the specs we wanted
#ifndef __EMSCRIPTEN__
Mix_QuerySpec(&qFrequency, &qFormat, &qChannels);
debug(D_NORMAL, "spec: f=%d fmt=%d c=%d\n", qFrequency, qFormat, qChannels);
if (qFrequency != frequency || qFormat != format || qChannels != channels)
@@ -89,6 +90,7 @@ int OpenAudio(int frequency, Uint16 format, int channels, int chunkSize)
printf("Audio not what we want.\n");
return 1;
}
#endif
return 0;
}
@@ -370,6 +372,7 @@ static void SoundPlayAtPosition(
// When allocating new channels, need to reset their volume
Mix_Volume(-1, ConfigGetInt(&gConfig, "Sound.SoundVolume"));
}
#ifndef __EMSCRIPTEN__
Mix_SetPosition(channel, (Sint16)bearing, (Uint8)distance);
if (isMuffled)
{
@@ -378,6 +381,7 @@ static void SoundPlayAtPosition(
fprintf(stderr, "Mix_RegisterEffect: %s\n", Mix_GetError());
}
}
#endif
}
void SoundPlay(SoundDevice *device, Mix_Chunk *data)

View File

@@ -50,7 +50,12 @@
#include <stdbool.h>
#ifdef __EMSCRIPTEN__
#include <SDL.h>
#include <SDL/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include "c_array.h"
#include "c_hashmap/hashmap.h"

View File

@@ -48,7 +48,12 @@
*/
#pragma once
#ifdef __EMSCRIPTEN__
#include <SDL.h>
#include <SDL/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include "c_array.h"
#include "game_events.h"

View File

@@ -46,6 +46,8 @@
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __EMSCRIPTEN__
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -1421,3 +1423,4 @@ int main(int argc, char *argv[])
exit(EXIT_SUCCESS);
}
#endif //__EMSCRIPTEN__

68
src/emscripten_loop.h Normal file
View File

@@ -0,0 +1,68 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2017, Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#ifdef __EMSCRIPTEN__
#include <stdbool.h>
#include "credits.h"
#include "cdogs/campaigns.h"
#include "cdogs/game_mode.h"
struct emscripten_context_t {
GameMode lastGameMode;
bool wasClient;
credits_displayer_t *creditsDisplayer;
custom_campaigns_t *campaigns;
};
struct emscripten_context_t EmscriptenContext;
void EmscriptenMainLoop(void *arg);
#endif

View File

@@ -36,6 +36,9 @@
#include "net_server.h"
#include "sounds.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
GameLoopData *GameLoopDataNew(
void *data,
@@ -93,9 +96,96 @@ typedef struct
int FramesSkipped;
int MaxFrameskip;
} LoopRunParams;
typedef struct
{
LoopRunner *l;
GameLoopData *data;
LoopRunParams p;
}LoopRunInnerData;
static LoopRunParams LoopRunParamsNew(const GameLoopData *data);
static bool LoopRunParamsShouldSleep(LoopRunParams *p);
static bool LoopRunParamsShouldSkip(LoopRunParams *p);
bool LoopRunnerRunInner(LoopRunInnerData *ctx)
{
// Frame rate control
if (LoopRunParamsShouldSleep(&(ctx->p)))
{
SDL_Delay(1);
return true;
}
// Input
if ((ctx->data->Frames & 1) || !ctx->data->InputEverySecondFrame)
{
EventPoll(&gEventHandlers, ctx->p.TicksNow);
if (ctx->data->InputFunc)
{
ctx->data->InputFunc(ctx->data);
}
}
NetClientPoll(&gNetClient);
NetServerPoll(&gNetServer);
// Update
ctx->p.Result = ctx->data->UpdateFunc(ctx->data, ctx->l);
GameLoopData *newData = GetCurrentLoop(ctx->l);
if (newData == NULL)
{
return false;
}
else if (newData != ctx->data)
{
// State change; restart loop
GameLoopOnExit(ctx->data);
ctx->data = newData;
GameLoopOnEnter(ctx->data);
ctx->p = LoopRunParamsNew(ctx->data);
return true;
}
NetServerFlush(&gNetServer);
NetClientFlush(&gNetClient);
bool draw = !ctx->data->HasDrawnFirst;
switch (ctx->p.Result)
{
case UPDATE_RESULT_OK:
// Do nothing
break;
case UPDATE_RESULT_DRAW:
draw = true;
break;
default:
CASSERT(false, "Unknown loop result");
break;
}
ctx->data->Frames++;
// frame skip
if (LoopRunParamsShouldSkip(&(ctx->p)))
{
return true;
}
// Draw
if (draw)
{
if (ctx->data->DrawFunc)
{
ctx->data->DrawFunc(ctx->data);
BlitFlip(&gGraphicsDevice);
}
ctx->data->HasDrawnFirst = true;
}
}
#ifdef __EMSCRIPTEN__
void EmscriptenMainLoop(void *arg)
{
// struct emscripten_context_t *ctx = arg;
LoopRunnerRunInner((LoopRunInnerData *)arg);
}
#endif
void LoopRunnerRun(LoopRunner *l)
{
GameLoopData *data = GetCurrentLoop(l);
@@ -105,80 +195,23 @@ void LoopRunnerRun(LoopRunner *l)
}
GameLoopOnEnter(data);
LoopRunParams p = LoopRunParamsNew(data);
LoopRunInnerData ctx;
ctx.l = l;
ctx.data = data;
ctx.p = LoopRunParamsNew(data);
#ifdef __EMSCRIPTEN__
// TODO use GameLoopData->FPS instead of 30?
emscripten_set_main_loop_arg(EmscriptenMainLoop, &ctx, 0, 1);
#else
for (;;)
{
// Frame rate control
if (LoopRunParamsShouldSleep(&p))
{
SDL_Delay(1);
continue;
}
// Input
if ((data->Frames & 1) || !data->InputEverySecondFrame)
{
EventPoll(&gEventHandlers, p.TicksNow);
if (data->InputFunc)
{
data->InputFunc(data);
}
}
NetClientPoll(&gNetClient);
NetServerPoll(&gNetServer);
// Update
p.Result = data->UpdateFunc(data, l);
GameLoopData *newData = GetCurrentLoop(l);
if (newData == NULL)
{
break;
}
else if (newData != data)
{
// State change; restart loop
GameLoopOnExit(data);
data = newData;
GameLoopOnEnter(data);
p = LoopRunParamsNew(data);
continue;
}
NetServerFlush(&gNetServer);
NetClientFlush(&gNetClient);
bool draw = !data->HasDrawnFirst;
switch (p.Result)
{
case UPDATE_RESULT_OK:
// Do nothing
break;
case UPDATE_RESULT_DRAW:
draw = true;
break;
default:
CASSERT(false, "Unknown loop result");
break;
}
data->Frames++;
// frame skip
if (LoopRunParamsShouldSkip(&p))
{
continue;
}
// Draw
if (draw)
{
if (data->DrawFunc)
{
data->DrawFunc(data);
BlitFlip(&gGraphicsDevice);
}
data->HasDrawnFirst = true;
}
if (!LoopRunnerRunInner(&ctx))
{
break;
}
}
#endif
GameLoopOnExit(data);
}
static LoopRunParams LoopRunParamsNew(const GameLoopData *data)