Browse Source

Reduce demo message size

`DemoMsg` struct and event data are now kept separately.

RAM usage for the timedemo messages: -85% (1.8 MiB -> 261 KiB)

Demo (`.dmo`) file size: -57% (590,790 bytes -> 253,410 bytes)
pull/6526/head
Gleb Mazovetskiy 3 years ago
parent
commit
eb51a8beb3
  1. 518
      Source/engine/demomode.cpp
  2. 10
      Source/interfac.cpp
  3. 8
      Source/interfac.h
  4. BIN
      test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo

518
Source/engine/demomode.cpp

@ -3,6 +3,10 @@
#include <cstdint>
#include <cstdio>
#include <deque>
#include <limits>
#include <optional>
#include <fmt/format.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
@ -36,27 +40,21 @@ enum class LoadingStatus : uint8_t {
UnsupportedVersion,
};
enum class DemoMsgType : uint8_t {
GameTick = 0,
Rendering = 1,
Message = 2,
};
struct MouseMotionEventData {
uint16_t x;
uint16_t y;
};
struct MouseButtonEventData {
uint8_t button;
uint16_t x;
uint16_t y;
uint16_t mod;
uint8_t button;
};
struct MouseWheelEventData {
int32_t x;
int32_t y;
int16_t x;
int16_t y;
uint16_t mod;
};
@ -65,16 +63,39 @@ struct KeyEventData {
uint16_t mod;
};
union DemoMsgEventData {
MouseMotionEventData motion;
MouseButtonEventData button;
MouseWheelEventData wheel;
KeyEventData key;
};
struct DemoMsg {
DemoMsgType type;
uint8_t progressToNextGameTick;
uint32_t eventType;
union {
MouseMotionEventData motion;
MouseButtonEventData button;
MouseWheelEventData wheel;
KeyEventData key;
enum EventType : uint8_t {
GameTick = 0,
Rendering = 1,
// Inputs:
MinEvent = 8,
QuitEvent = 8,
MouseMotionEvent = 9,
MouseButtonDownEvent = 10,
MouseButtonUpEvent = 11,
MouseWheelEvent = 12,
KeyDownEvent = 13,
KeyUpEvent = 14,
MinCustomEvent = 64,
};
EventType type;
uint8_t progressToNextGameTick;
[[nodiscard]] bool isEvent() const
{
return type >= MinEvent;
}
};
int DemoNumber = -1;
@ -111,16 +132,55 @@ struct {
} DemoSettings;
FILE *DemoRecording;
std::deque<DemoMsg> Demo_Message_Queue;
uint32_t DemoModeLastTick = 0;
int LogicTick = 0;
int StartTime = 0;
uint32_t StartTime = 0;
uint16_t DemoGraphicsWidth = 640;
uint16_t DemoGraphicsHeight = 480;
void ReadSettings(FILE *in, uint8_t version)
std::deque<DemoMsg> DemoMessageQueue;
std::deque<MouseMotionEventData> MouseMotionEventDataQueue;
std::deque<MouseButtonEventData> MouseButtonEventDataQueue;
std::deque<MouseWheelEventData> MouseWheelEventDataQueue;
std::deque<KeyEventData> KeyEventDataQueue;
struct DemoMessageAndData {
DemoMsg message;
DemoMsgEventData data;
};
DemoMessageAndData PopDemoMessage()
{
DemoMessageAndData result;
result.message = DemoMessageQueue.front();
DemoMessageQueue.pop_front();
switch (result.message.type) {
case DemoMsg::MouseMotionEvent:
result.data.motion = MouseMotionEventDataQueue.front();
MouseMotionEventDataQueue.pop_front();
break;
case DemoMsg::MouseButtonDownEvent:
case DemoMsg::MouseButtonUpEvent:
result.data.button = MouseButtonEventDataQueue.front();
MouseButtonEventDataQueue.pop_front();
break;
case DemoMsg::MouseWheelEvent:
result.data.wheel = MouseWheelEventDataQueue.front();
MouseWheelEventDataQueue.pop_front();
break;
case DemoMsg::KeyDownEvent:
case DemoMsg::KeyUpEvent:
result.data.key = KeyEventDataQueue.front();
KeyEventDataQueue.pop_front();
break;
default:
break;
}
return result;
}
void ReadSettings(FILE *in, uint8_t version) // NOLINT(readability-identifier-length)
{
DemoGraphicsWidth = ReadLE16(in);
DemoGraphicsHeight = ReadLE16(in);
@ -157,23 +217,23 @@ void WriteSettings(FILE *out)
{
WriteLE16(out, gnScreenWidth);
WriteLE16(out, gnScreenHeight);
WriteByte(out, *sgOptions.Gameplay.runInTown);
WriteByte(out, *sgOptions.Gameplay.theoQuest);
WriteByte(out, *sgOptions.Gameplay.cowQuest);
WriteByte(out, *sgOptions.Gameplay.autoGoldPickup);
WriteByte(out, *sgOptions.Gameplay.autoElixirPickup);
WriteByte(out, *sgOptions.Gameplay.autoOilPickup);
WriteByte(out, *sgOptions.Gameplay.autoPickupInTown);
WriteByte(out, *sgOptions.Gameplay.adriaRefillsMana);
WriteByte(out, *sgOptions.Gameplay.autoEquipWeapons);
WriteByte(out, *sgOptions.Gameplay.autoEquipArmor);
WriteByte(out, *sgOptions.Gameplay.autoEquipHelms);
WriteByte(out, *sgOptions.Gameplay.autoEquipShields);
WriteByte(out, *sgOptions.Gameplay.autoEquipJewelry);
WriteByte(out, *sgOptions.Gameplay.randomizeQuests);
WriteByte(out, *sgOptions.Gameplay.showItemLabels);
WriteByte(out, *sgOptions.Gameplay.autoRefillBelt);
WriteByte(out, *sgOptions.Gameplay.disableCripplingShrines);
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.runInTown));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.theoQuest));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.cowQuest));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoGoldPickup));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoElixirPickup));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoOilPickup));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoPickupInTown));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.adriaRefillsMana));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoEquipWeapons));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoEquipArmor));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoEquipHelms));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoEquipShields));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoEquipJewelry));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.randomizeQuests));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.showItemLabels));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.autoRefillBelt));
WriteByte(out, static_cast<uint8_t>(*sgOptions.Gameplay.disableCripplingShrines));
WriteByte(out, *sgOptions.Gameplay.numHealPotionPickup);
WriteByte(out, *sgOptions.Gameplay.numFullHealPotionPickup);
WriteByte(out, *sgOptions.Gameplay.numManaPotionPickup);
@ -183,43 +243,47 @@ void WriteSettings(FILE *out)
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState)
bool CreateSdlEvent(const DemoMsg &dmsg, const DemoMsgEventData &data, SDL_Event &event, uint16_t &modState)
{
event.type = dmsg.eventType;
switch (static_cast<SDL_EventType>(dmsg.eventType)) {
case SDL_MOUSEMOTION:
const uint8_t type = dmsg.type;
switch (type) {
case DemoMsg::MouseMotionEvent:
event.type = SDL_MOUSEMOTION;
event.motion.which = 0;
event.motion.x = dmsg.motion.x;
event.motion.y = dmsg.motion.y;
event.motion.x = data.motion.x;
event.motion.y = data.motion.y;
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case DemoMsg::MouseButtonDownEvent:
case DemoMsg::MouseButtonUpEvent:
event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
event.button.which = 0;
event.button.button = dmsg.button.button;
event.button.state = dmsg.eventType == SDL_MOUSEBUTTONDOWN ? SDL_PRESSED : SDL_RELEASED;
event.button.x = dmsg.button.x;
event.button.y = dmsg.button.y;
modState = dmsg.button.mod;
event.button.button = data.button.button;
event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED;
event.button.x = data.button.x;
event.button.y = data.button.y;
modState = data.button.mod;
return true;
case SDL_MOUSEWHEEL:
case DemoMsg::MouseWheelEvent:
event.type = SDL_MOUSEWHEEL;
event.wheel.which = 0;
event.wheel.x = dmsg.wheel.x;
event.wheel.y = dmsg.wheel.y;
modState = dmsg.wheel.mod;
event.wheel.x = data.wheel.x;
event.wheel.y = data.wheel.y;
modState = data.wheel.mod;
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
event.key.state = dmsg.eventType == SDL_KEYDOWN ? SDL_PRESSED : SDL_RELEASED;
event.key.keysym.sym = dmsg.key.sym;
event.key.keysym.mod = dmsg.key.mod;
case DemoMsg::KeyDownEvent:
case DemoMsg::KeyUpEvent:
event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP;
event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED;
event.key.keysym.sym = data.key.sym;
event.key.keysym.mod = data.key.mod;
return true;
default:
if (dmsg.eventType >= SDL_USEREVENT) {
event.type = CustomEventToSdlEvent(static_cast<interface_mode>(dmsg.eventType - SDL_USEREVENT));
if (type >= DemoMsg::MinCustomEvent) {
event.type = CustomEventToSdlEvent(static_cast<interface_mode>(type - DemoMsg::MinCustomEvent));
return true;
}
event.type = static_cast<SDL_EventType>(0);
LogWarn("Unsupported demo event (type={:x})", dmsg.eventType);
LogWarn("Unsupported demo event (type={})", type);
return false;
}
}
@ -272,120 +336,139 @@ uint8_t Sdl2ToSdl1MouseButton(uint8_t button)
}
}
bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState)
bool CreateSdlEvent(const DemoMsg &dmsg, const DemoMsgEventData &data, SDL_Event &event, uint16_t &modState)
{
switch (dmsg.eventType) {
case 0x400:
const uint8_t type = dmsg.type;
switch (type) {
case DemoMsg::MouseMotionEvent:
event.type = SDL_MOUSEMOTION;
event.motion.which = 0;
event.motion.x = dmsg.motion.x;
event.motion.y = dmsg.motion.y;
event.motion.x = data.motion.x;
event.motion.y = data.motion.y;
return true;
case 0x401:
case 0x402:
event.type = dmsg.eventType == 0x401 ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
case DemoMsg::MouseButtonDownEvent:
case DemoMsg::MouseButtonUpEvent:
event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
event.button.which = 0;
event.button.button = Sdl2ToSdl1MouseButton(dmsg.button.button);
event.button.state = dmsg.eventType == 0x401 ? SDL_PRESSED : SDL_RELEASED;
event.button.x = dmsg.button.x;
event.button.y = dmsg.button.y;
modState = dmsg.button.mod;
event.button.button = Sdl2ToSdl1MouseButton(data.button.button);
event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED;
event.button.x = data.button.x;
event.button.y = data.button.y;
modState = data.button.mod;
return true;
case 0x403: // SDL_MOUSEWHEEL
if (dmsg.wheel.y == 0) {
case DemoMsg::MouseWheelEvent:
if (data.wheel.y == 0) {
LogWarn("Demo: unsupported event (mouse wheel y == 0)");
return false;
}
event.type = SDL_MOUSEBUTTONDOWN;
event.button.which = 0;
event.button.button = dmsg.wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
modState = dmsg.wheel.mod;
event.button.button = data.wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
modState = data.wheel.mod;
return true;
case 0x300:
case 0x301:
event.type = dmsg.eventType == 0x300 ? SDL_KEYDOWN : SDL_KEYUP;
case DemoMsg::KeyDownEvent:
case DemoMsg::KeyUpEvent:
event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP;
event.key.which = 0;
event.key.state = dmsg.eventType == 0x300 ? SDL_PRESSED : SDL_RELEASED;
event.key.keysym.sym = Sdl2ToSdl1Key(dmsg.key.sym);
event.key.keysym.mod = static_cast<SDL_Keymod>(dmsg.key.mod);
event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED;
event.key.keysym.sym = Sdl2ToSdl1Key(data.key.sym);
event.key.keysym.mod = static_cast<SDL_Keymod>(data.key.mod);
return true;
default:
if (dmsg.eventType >= 0x8000) {
event.type = CustomEventToSdlEvent(static_cast<interface_mode>(dmsg.eventType - 0x8000));
if (type >= DemoMsg::MinCustomEvent) {
event.type = CustomEventToSdlEvent(static_cast<interface_mode>(type - DemoMsg::MinCustomEvent));
return true;
}
event.type = static_cast<SDL_EventType>(0);
LogWarn("Demo: unsupported event (type={:x})", dmsg.eventType);
LogWarn("Demo: unsupported event (type={:x})", type);
return false;
}
}
#endif
void LogDemoMessage(const DemoMsg &msg)
uint8_t MapPreV2DemoMsgEventType(uint16_t type)
{
switch (type) {
case 0x100:
return DemoMsg::QuitEvent;
case 0x300:
return DemoMsg::KeyDownEvent;
case 0x301:
return DemoMsg::KeyUpEvent;
case 0x400:
return DemoMsg::MouseMotionEvent;
case 0x401:
return DemoMsg::MouseButtonDownEvent;
case 0x402:
return DemoMsg::MouseButtonUpEvent;
case 0x403:
return DemoMsg::MouseWheelEvent;
default:
if (type < 0x8000) { // SDL_USEREVENT
app_fatal(StrCat("Unknown event ", type));
}
return DemoMsg::MinCustomEvent + (type - 0x8000);
}
}
void LogDemoMessage(const DemoMsg &msg, const DemoMsgEventData &data = DemoMsgEventData {})
{
#ifdef LOG_DEMOMODE_MESSAGES
const uint8_t progressToNextGameTick = msg.progressToNextGameTick;
switch (msg.type) {
case DemoMsgType::Message: {
const uint32_t eventType = msg.eventType;
switch (eventType) {
case 0x400: // SDL_MOUSEMOTION
#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION
Log("🖱 Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick,
msg.motion.x, msg.motion.y);
#endif
break;
case 0x401: // SDL_MOUSEBUTTONDOWN
case 0x402: // SDL_MOUSEBUTTONUP
Log("🖱 Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick,
eventType == 0x401 ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP",
msg.button.button, msg.button.x, msg.button.y, msg.button.mod);
break;
case 0x403: // SDL_MOUSEWHEEL
Log("🖱 Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick,
msg.wheel.x, msg.wheel.y, msg.wheel.mod);
break;
case 0x300: // SDL_KEYDOWN
case 0x301: // SDL_KEYUP
Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick,
eventType == 0x300 ? "KEYDOWN" : "KEYUP",
msg.key.sym, msg.key.mod);
break;
case 0x100: // SDL_QUIT
Log("❎ Message {:>3} QUIT", progressToNextGameTick);
break;
default:
Log("📨 Message {:>3} USEREVENT 0x{:x}", progressToNextGameTick, eventType);
break;
}
} break;
case DemoMsgType::GameTick:
case DemoMsg::GameTick:
#ifdef LOG_DEMOMODE_MESSAGES_GAMETICK
Log(" GameTick {:>3}", progressToNextGameTick);
#endif
break;
case DemoMsgType::Rendering:
case DemoMsg::Rendering:
#ifdef LOG_DEMOMODE_MESSAGES_RENDERING
Log("🖼 Rendering {:>3}", progressToNextGameTick);
#endif
break;
case DemoMsg::MouseMotionEvent:
#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION
Log("🖱 Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick,
data.motion.x, data.motion.y);
#endif
break;
case DemoMsg::MouseButtonDownEvent:
case DemoMsg::MouseButtonUpEvent:
Log("🖱 Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick,
msg.type == DemoMsg::MouseButtonDownEvent ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP",
data.button.button, data.button.x, data.button.y, data.button.mod);
break;
case DemoMsg::MouseWheelEvent:
Log("🖱 Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick,
data.wheel.x, data.wheel.y, data.wheel.mod);
break;
case DemoMsg::KeyDownEvent:
case DemoMsg::KeyUpEvent:
Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick,
msg.type == DemoMsg::KeyDownEvent ? "KEYDOWN" : "KEYUP",
data.key.sym, data.key.mod);
break;
case DemoMsg::QuitEvent:
Log("❎ Message {:>3} QUIT", progressToNextGameTick);
break;
default:
LogError("INVALID DEMO MODE MESSAGE {} {:>3}", static_cast<uint32_t>(msg.type), progressToNextGameTick);
Log("📨 Message {:>3} USEREVENT {}", progressToNextGameTick, static_cast<uint8_t>(msg.type));
break;
}
#endif // LOG_DEMOMODE_MESSAGES
}
LoadingStatus LoadDemoMessages(int i)
LoadingStatus LoadDemoMessages(int demoNumber)
{
const std::string path = StrCat(paths::PrefPath(), "demo_", i, ".dmo");
const std::string path = StrCat(paths::PrefPath(), "demo_", demoNumber, ".dmo");
FILE *demofile = OpenFile(path.c_str(), "rb");
if (demofile == nullptr) {
return LoadingStatus::FileNotFound;
}
const uint8_t version = ReadByte(demofile);
if (version != 0 && version != 1) {
if (version > 2) {
return LoadingStatus::UnsupportedVersion;
}
@ -393,53 +476,59 @@ LoadingStatus LoadDemoMessages(int i)
ReadSettings(demofile, version);
while (true) {
const uint32_t typeNum = ReadLE32(demofile);
if (std::feof(demofile))
const uint8_t typeNum = version >= 2 ? ReadByte(demofile) : ReadLE32(demofile);
if (std::feof(demofile) != 0)
break;
const auto type = static_cast<DemoMsgType>(typeNum);
const uint8_t progressToNextGameTick = ReadByte(demofile);
switch (type) {
case DemoMsgType::Message: {
const uint32_t eventType = ReadLE32(demofile);
DemoMsg msg { type, progressToNextGameTick, eventType, {} };
switch (typeNum) {
case DemoMsg::GameTick:
case DemoMsg::Rendering:
DemoMessageQueue.push_back(DemoMsg { static_cast<DemoMsg::EventType>(typeNum), progressToNextGameTick });
break;
default: {
const uint8_t eventType = version >= 2 ? typeNum : MapPreV2DemoMsgEventType(static_cast<uint16_t>(ReadLE32(demofile)));
DemoMessageQueue.push_back(DemoMsg { static_cast<DemoMsg::EventType>(eventType), progressToNextGameTick });
switch (eventType) {
case 0x400: // SDL_MOUSEMOTION
msg.motion.x = ReadLE16(demofile);
msg.motion.y = ReadLE16(demofile);
break;
case 0x401: // SDL_MOUSEBUTTONDOWN
case 0x402: // SDL_MOUSEBUTTONUP
msg.button.button = ReadByte(demofile);
msg.button.x = ReadLE16(demofile);
msg.button.y = ReadLE16(demofile);
msg.button.mod = ReadLE16(demofile);
break;
case 0x403: // SDL_MOUSEWHEEL
msg.wheel.x = ReadLE32<int32_t>(demofile);
msg.wheel.y = ReadLE32<int32_t>(demofile);
msg.wheel.mod = ReadLE16(demofile);
break;
case 0x300: // SDL_KEYDOWN
case 0x301: // SDL_KEYUP
msg.key.sym = static_cast<SDL_Keycode>(ReadLE32(demofile));
msg.key.mod = static_cast<SDL_Keymod>(ReadLE16(demofile));
break;
case 0x100: // SDL_QUIT
case DemoMsg::MouseMotionEvent: {
MouseMotionEventData motion;
motion.x = ReadLE16(demofile);
motion.y = ReadLE16(demofile);
MouseMotionEventDataQueue.push_back(motion);
} break;
case DemoMsg::MouseButtonDownEvent:
case DemoMsg::MouseButtonUpEvent: {
MouseButtonEventData button;
button.button = ReadByte(demofile);
button.x = ReadLE16(demofile);
button.y = ReadLE16(demofile);
button.mod = ReadLE16(demofile);
MouseButtonEventDataQueue.push_back(button);
} break;
case DemoMsg::MouseWheelEvent: {
MouseWheelEventData wheel;
wheel.x = version >= 2 ? ReadLE16<int16_t>(demofile) : static_cast<int16_t>(ReadLE32<int32_t>(demofile));
wheel.y = version >= 2 ? ReadLE16<int16_t>(demofile) : static_cast<int16_t>(ReadLE32<int32_t>(demofile));
wheel.mod = ReadLE16(demofile);
MouseWheelEventDataQueue.push_back(wheel);
} break;
case DemoMsg::KeyDownEvent:
case DemoMsg::KeyUpEvent: {
KeyEventData key;
key.sym = static_cast<SDL_Keycode>(ReadLE32(demofile));
key.mod = static_cast<SDL_Keymod>(ReadLE16(demofile));
KeyEventDataQueue.push_back(key);
} break;
case DemoMsg::QuitEvent: // SDL_QUIT
break;
default:
if (eventType < 0x8000) { // SDL_USEREVENT
if (eventType < DemoMsg::MinCustomEvent) {
app_fatal(StrCat("Unknown event ", eventType));
}
break;
}
Demo_Message_Queue.push_back(msg);
break;
}
default:
Demo_Message_Queue.push_back(DemoMsg { type, progressToNextGameTick, 0, {} });
break;
} break;
}
}
@ -450,11 +539,10 @@ LoadingStatus LoadDemoMessages(int i)
return LoadingStatus::Success;
}
void RecordEventHeader(const SDL_Event &event)
void WriteDemoMsgHeader(DemoMsg::EventType type)
{
WriteLE32(DemoRecording, static_cast<uint32_t>(DemoMsgType::Message));
WriteByte(DemoRecording, type);
WriteByte(DemoRecording, ProgressToNextGameTick);
WriteLE32(DemoRecording, event.type);
}
} // namespace
@ -467,7 +555,7 @@ void InitPlayBack(int demoNumber, bool timedemo)
Timedemo = timedemo;
ControlMode = ControlTypes::KeyboardAndMouse;
LoadingStatus status = LoadDemoMessages(demoNumber);
const LoadingStatus status = LoadDemoMessages(demoNumber);
switch (status) {
case LoadingStatus::Success:
return;
@ -540,29 +628,29 @@ bool IsRecording()
bool GetRunGameLoop(bool &drawGame, bool &processInput)
{
if (Demo_Message_Queue.empty())
if (DemoMessageQueue.empty())
app_fatal("Demo queue empty");
const DemoMsg dmsg = Demo_Message_Queue.front();
const DemoMsg &dmsg = DemoMessageQueue.front();
if (dmsg.isEvent())
app_fatal("Unexpected event demo message in GetRunGameLoop");
LogDemoMessage(dmsg);
if (dmsg.type == DemoMsgType::Message)
app_fatal("Unexpected Message");
if (Timedemo) {
// disable additonal rendering to speedup replay
drawGame = dmsg.type == DemoMsgType::GameTick && !HeadlessMode;
drawGame = dmsg.type == DemoMsg::GameTick && !HeadlessMode;
} else {
int currentTickCount = SDL_GetTicks();
int ticksElapsed = currentTickCount - DemoModeLastTick;
bool tickDue = ticksElapsed >= gnTickDelay;
drawGame = false;
if (tickDue) {
if (dmsg.type == DemoMsgType::GameTick) {
if (dmsg.type == DemoMsg::GameTick) {
DemoModeLastTick = currentTickCount;
}
} else {
int32_t fraction = ticksElapsed * AnimationInfo::baseValueFraction / gnTickDelay;
fraction = std::clamp<int32_t>(fraction, 0, AnimationInfo::baseValueFraction);
uint8_t progressToNextGameTick = static_cast<uint8_t>(fraction);
if (dmsg.type == DemoMsgType::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) {
if (dmsg.type == DemoMsg::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) {
// we are ahead of the replay => add a additional rendering for smoothness
if (gbRunGame && PauseMode == 0 && (gbIsMultiplayer || !gmenu_is_active()) && gbProcessPlayers) // if game is not running or paused there is no next gametick in the near future
ProgressToNextGameTick = progressToNextGameTick;
@ -573,10 +661,11 @@ bool GetRunGameLoop(bool &drawGame, bool &processInput)
}
}
ProgressToNextGameTick = dmsg.progressToNextGameTick;
Demo_Message_Queue.pop_front();
if (dmsg.type == DemoMsgType::GameTick)
const bool isGameTick = dmsg.type == DemoMsg::GameTick;
DemoMessageQueue.pop_front();
if (isGameTick)
LogicTick++;
return dmsg.type == DemoMsgType::GameTick;
return isGameTick;
}
bool FetchMessage(SDL_Event *event, uint16_t *modState)
@ -591,7 +680,7 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState)
return true;
}
if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) {
Demo_Message_Queue.clear();
DemoMessageQueue.clear();
DemoNumber = -1;
Timedemo = false;
last_tick = SDL_GetTicks();
@ -608,14 +697,15 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState)
}
}
if (!Demo_Message_Queue.empty()) {
const DemoMsg dmsg = Demo_Message_Queue.front();
LogDemoMessage(dmsg);
if (dmsg.type == DemoMsgType::Message) {
const bool hasEvent = CreateSdlEvent(dmsg, *event, *modState);
ProgressToNextGameTick = dmsg.progressToNextGameTick;
Demo_Message_Queue.pop_front();
if (!DemoMessageQueue.empty()) {
if (DemoMessageQueue.front().isEvent()) {
const DemoMessageAndData dmsg = PopDemoMessage();
LogDemoMessage(dmsg.message, dmsg.data);
const bool hasEvent = CreateSdlEvent(dmsg.message, dmsg.data, *event, *modState);
ProgressToNextGameTick = dmsg.message.progressToNextGameTick;
return hasEvent;
} else {
LogDemoMessage(DemoMessageQueue.front());
}
}
@ -624,8 +714,7 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState)
void RecordGameLoopResult(bool runGameLoop)
{
WriteLE32(DemoRecording, static_cast<uint32_t>(runGameLoop ? DemoMsgType::GameTick : DemoMsgType::Rendering));
WriteByte(DemoRecording, ProgressToNextGameTick);
WriteDemoMsgHeader(runGameLoop ? DemoMsg::GameTick : DemoMsg::Rendering);
}
void RecordMessage(const SDL_Event &event, uint16_t modState)
@ -636,49 +725,64 @@ void RecordMessage(const SDL_Event &event, uint16_t modState)
return;
switch (event.type) {
case SDL_MOUSEMOTION:
RecordEventHeader(event);
WriteDemoMsgHeader(DemoMsg::MouseMotionEvent);
WriteLE16(DemoRecording, event.motion.x);
WriteLE16(DemoRecording, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
RecordEventHeader(event);
WriteByte(DemoRecording, event.button.button);
WriteLE16(DemoRecording, event.button.x);
WriteLE16(DemoRecording, event.button.y);
WriteLE16(DemoRecording, modState);
#ifdef USE_SDL1
if (event.button.button == SDL_BUTTON_WHEELUP || event.button.button == SDL_BUTTON_WHEELDOWN) {
WriteDemoMsgHeader(DemoMsg::MouseWheelEvent);
WriteLE16(DemoRecording, 0);
WriteLE16(DemoRecording, event.button.button == SDL_BUTTON_WHEELUP ? 1 : -1);
WriteLE16(DemoRecording, modState);
} else {
#endif
WriteDemoMsgHeader(event.type == SDL_MOUSEBUTTONDOWN ? DemoMsg::MouseButtonDownEvent : DemoMsg::MouseButtonUpEvent);
WriteByte(DemoRecording, event.button.button);
WriteLE16(DemoRecording, event.button.x);
WriteLE16(DemoRecording, event.button.y);
WriteLE16(DemoRecording, modState);
#ifdef USE_SDL1
}
#endif
break;
#ifndef USE_SDL1
case SDL_MOUSEWHEEL:
RecordEventHeader(event);
WriteLE32(DemoRecording, event.wheel.x);
WriteLE32(DemoRecording, event.wheel.y);
WriteDemoMsgHeader(DemoMsg::MouseWheelEvent);
if (event.wheel.x < std::numeric_limits<int16_t>::min()
|| event.wheel.x > std::numeric_limits<int16_t>::max()
|| event.wheel.y < std::numeric_limits<int16_t>::min()
|| event.wheel.y > std::numeric_limits<int16_t>::max()) {
app_fatal(fmt::format("Mouse wheel event x/y out of int16_t range. x={} y={}",
event.wheel.x, event.wheel.y));
}
WriteLE16(DemoRecording, event.wheel.x);
WriteLE16(DemoRecording, event.wheel.y);
WriteLE16(DemoRecording, modState);
break;
#endif
case SDL_KEYDOWN:
case SDL_KEYUP:
RecordEventHeader(event);
WriteDemoMsgHeader(event.type == SDL_KEYDOWN ? DemoMsg::KeyDownEvent : DemoMsg::KeyUpEvent);
WriteLE32(DemoRecording, static_cast<uint32_t>(event.key.keysym.sym));
WriteLE16(DemoRecording, static_cast<uint16_t>(event.key.keysym.mod));
break;
#ifndef USE_SDL1
case SDL_WINDOWEVENT:
if (event.window.type == SDL_WINDOWEVENT_CLOSE) {
SDL_Event quitEvent;
quitEvent.type = SDL_QUIT;
RecordEventHeader(quitEvent);
WriteDemoMsgHeader(DemoMsg::QuitEvent);
}
break;
#endif
case SDL_QUIT:
RecordEventHeader(event);
WriteDemoMsgHeader(DemoMsg::QuitEvent);
break;
default:
if (IsCustomEvent(event.type)) {
SDL_Event stableCustomEvent;
stableCustomEvent.type = SDL_USEREVENT + static_cast<uint32_t>(GetCustomEvent(event.type));
RecordEventHeader(stableCustomEvent);
WriteDemoMsgHeader(static_cast<DemoMsg::EventType>(
DemoMsg::MinCustomEvent + static_cast<uint8_t>(GetCustomEvent(event.type))));
}
break;
}
@ -694,7 +798,7 @@ void NotifyGameLoopStart()
LogError("Failed to open {} for writing", path);
return;
}
constexpr uint8_t Version = 1;
constexpr uint8_t Version = 2;
WriteByte(DemoRecording, Version);
WriteLE32(DemoRecording, gSaveNumber);
WriteSettings(DemoRecording);
@ -719,7 +823,7 @@ void NotifyGameLoopEnd()
}
if (IsRunning() && !HeadlessMode) {
float seconds = (SDL_GetTicks() - StartTime) / 1000.0f;
const float seconds = (SDL_GetTicks() - StartTime) / 1000.0F;
SDL_Log("%d frames, %.2f seconds: %.1f fps", LogicTick, seconds, LogicTick / seconds);
gbRunGameResult = false;
gbRunGame = false;

10
Source/interfac.cpp

@ -46,8 +46,8 @@ const int BarPos[3][2] = { { 53, 37 }, { 53, 421 }, { 53, 37 } };
OptionalOwnedClxSpriteList ArtCutsceneWidescreen;
uint32_t CustomEventsBegin = SDL_USEREVENT;
constexpr uint32_t NumCustomEvents = WM_LAST - WM_FIRST + 1;
uint16_t CustomEventsBegin = SDL_USEREVENT;
constexpr uint16_t NumCustomEvents = WM_LAST - WM_FIRST + 1;
Cutscenes GetCutSceneFromLevelType(dungeon_type type)
{
@ -234,17 +234,17 @@ void RegisterCustomEvents()
#endif
}
bool IsCustomEvent(uint32_t eventType)
bool IsCustomEvent(uint16_t eventType)
{
return eventType >= CustomEventsBegin && eventType < CustomEventsBegin + NumCustomEvents;
}
interface_mode GetCustomEvent(uint32_t eventType)
interface_mode GetCustomEvent(uint16_t eventType)
{
return static_cast<interface_mode>(eventType - CustomEventsBegin);
}
uint32_t CustomEventToSdlEvent(interface_mode eventType)
uint16_t CustomEventToSdlEvent(interface_mode eventType)
{
return CustomEventsBegin + eventType;
}

8
Source/interfac.h

@ -14,7 +14,7 @@ namespace devilution {
/**
* @brief Custom events.
*/
enum interface_mode : uint16_t {
enum interface_mode : uint8_t {
WM_DIABNEXTLVL = 0,
WM_DIABPREVLVL,
WM_DIABRTNLVL,
@ -32,11 +32,11 @@ enum interface_mode : uint16_t {
void RegisterCustomEvents();
bool IsCustomEvent(uint32_t eventType);
bool IsCustomEvent(uint16_t eventType);
interface_mode GetCustomEvent(uint32_t eventType);
interface_mode GetCustomEvent(uint16_t eventType);
uint32_t CustomEventToSdlEvent(interface_mode eventType);
uint16_t CustomEventToSdlEvent(interface_mode eventType);
enum Cutscenes : uint8_t {
CutStart,

BIN
test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo vendored

Binary file not shown.
Loading…
Cancel
Save