aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/camera.cpp10
-rw-r--r--src/client/camera.h3
-rw-r--r--src/client/client.cpp8
-rw-r--r--src/client/clientenvironment.cpp1
-rw-r--r--src/client/clientmap.cpp41
-rw-r--r--src/client/clientmap.h3
-rw-r--r--src/client/content_cao.cpp8
-rw-r--r--src/client/mapblock_mesh.cpp21
-rw-r--r--src/client/mapblock_mesh.h17
-rw-r--r--src/client/shader.cpp2
-rw-r--r--src/client/shadows/dynamicshadowsrender.cpp1
-rw-r--r--src/content/content.cpp7
-rw-r--r--src/content/content.h7
-rw-r--r--src/content/subgames.cpp46
-rw-r--r--src/content/subgames.h10
-rw-r--r--src/defaultsettings.cpp2
-rw-r--r--src/gui/guiConfirmRegistration.cpp17
-rw-r--r--src/gui/guiConfirmRegistration.h3
-rw-r--r--src/gui/guiFormSpecMenu.cpp59
-rw-r--r--src/gui/guiFormSpecMenu.h4
-rw-r--r--src/gui/guiKeyChangeMenu.cpp20
-rw-r--r--src/gui/guiKeyChangeMenu.h1
-rw-r--r--src/gui/guiPasswordChange.cpp19
-rw-r--r--src/gui/guiPasswordChange.h2
-rw-r--r--src/gui/guiPathSelectMenu.cpp3
-rw-r--r--src/gui/guiScrollContainer.cpp9
-rw-r--r--src/gui/guiVolumeChange.cpp22
-rw-r--r--src/gui/guiVolumeChange.h3
-rw-r--r--src/gui/mainmenumanager.h4
-rw-r--r--src/gui/modalMenu.cpp13
-rw-r--r--src/gui/modalMenu.h1
-rw-r--r--src/irrlicht_changes/CGUITTFont.cpp14
-rw-r--r--src/irrlicht_changes/CGUITTFont.h38
-rw-r--r--src/script/lua_api/l_mainmenu.cpp11
-rw-r--r--src/server.cpp3
-rw-r--r--src/unittest/test.cpp4
-rw-r--r--src/unittest/test.h2
-rw-r--r--src/unittest/test_serialization.cpp78
-rw-r--r--src/util/serialize.cpp220
39 files changed, 382 insertions, 355 deletions
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index 0c387262e..164db8761 100644
--- a/src/client/camera.cpp
+++ b/src/client/camera.cpp
@@ -48,7 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
- m_client(client)
+ m_client(client),
+ m_player_light_color(0xFFFFFFFF)
{
auto smgr = rendering_engine->get_scene_manager();
// note: making the camera node a child of the player node
@@ -154,8 +155,10 @@ void Camera::step(f32 dtime)
bool was_under_zero = m_wield_change_timer < 0;
m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
- if (m_wield_change_timer >= 0 && was_under_zero)
+ if (m_wield_change_timer >= 0 && was_under_zero) {
m_wieldnode->setItem(m_wield_item_next, m_client);
+ m_wieldnode->setNodeLightColor(m_player_light_color);
+ }
if (m_view_bobbing_state != 0)
{
@@ -556,7 +559,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation);
- m_wieldnode->setNodeLightColor(player->light_color);
+ m_player_light_color = player->light_color;
+ m_wieldnode->setNodeLightColor(m_player_light_color);
// Set render distance
updateViewingRange();
diff --git a/src/client/camera.h b/src/client/camera.h
index ecd71f1e7..6a8cf650d 100644
--- a/src/client/camera.h
+++ b/src/client/camera.h
@@ -291,4 +291,7 @@ private:
std::list<Nametag *> m_nametags;
bool m_show_nametag_backgrounds;
+
+ // Last known light color of the player
+ video::SColor m_player_light_color;
};
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 4e4bb8a97..2d9d226e4 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -789,16 +789,18 @@ void Client::peerAdded(con::Peer *peer)
infostream << "Client::peerAdded(): peer->id="
<< peer->id << std::endl;
}
+
void Client::deletingPeer(con::Peer *peer, bool timeout)
{
infostream << "Client::deletingPeer(): "
"Server Peer is getting deleted "
<< "(timeout=" << timeout << ")" << std::endl;
- if (timeout) {
- m_access_denied = true;
+ m_access_denied = true;
+ if (timeout)
m_access_denied_reason = gettext("Connection timed out.");
- }
+ else
+ m_access_denied_reason = gettext("Connection aborted (protocol error?).");
}
/*
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
index 01aaa0408..e847161f9 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -304,6 +304,7 @@ void ClientEnvironment::step(float dtime)
node_at_lplayer = m_map->getNode(p);
u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
+ lplayer->light_color = encode_light(light, 0); // this transfers light.alpha
final_color_blend(&lplayer->light_color, light, day_night_ratio);
}
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
index 51f0f6896..85b765709 100644
--- a/src/client/clientmap.cpp
+++ b/src/client/clientmap.cpp
@@ -449,15 +449,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) {
- scene::IMeshBuffer *buf;
-
- if (descriptor.m_use_partial_buffer) {
- descriptor.m_partial_buffer->beforeDraw();
- buf = descriptor.m_partial_buffer->getBuffer();
- }
- else {
- buf = descriptor.m_buffer;
- }
+ scene::IMeshBuffer *buf = descriptor.getBuffer();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 2000) {
@@ -489,6 +481,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Do not enable filter on shadow texture to avoid visual artifacts
// with colored shadows.
// Filtering is done in shader code anyway
+ layer.BilinearFilter = false;
+ layer.AnisotropicFilter = false;
layer.TrilinearFilter = false;
}
driver->setMaterial(material);
@@ -499,7 +493,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
+ descriptor.draw(driver);
vertex_count += buf->getIndexCount();
}
@@ -810,15 +804,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) {
- scene::IMeshBuffer *buf;
-
- if (descriptor.m_use_partial_buffer) {
- descriptor.m_partial_buffer->beforeDraw();
- buf = descriptor.m_partial_buffer->getBuffer();
- }
- else {
- buf = descriptor.m_buffer;
- }
+ scene::IMeshBuffer *buf = descriptor.getBuffer();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 1000) {
@@ -843,7 +829,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
+ descriptor.draw(driver);
vertex_count += buf->getIndexCount();
}
@@ -964,3 +950,18 @@ void ClientMap::updateTransparentMeshBuffers()
m_needs_update_transparent_meshes = false;
}
+scene::IMeshBuffer* ClientMap::DrawDescriptor::getBuffer()
+{
+ return m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer;
+}
+
+void ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver)
+{
+ if (m_use_partial_buffer) {
+ m_partial_buffer->beforeDraw();
+ driver->drawMeshBuffer(m_partial_buffer->getBuffer());
+ m_partial_buffer->afterDraw();
+ } else {
+ driver->drawMeshBuffer(m_buffer);
+ }
+}
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
index 6d57f1911..823870c68 100644
--- a/src/client/clientmap.h
+++ b/src/client/clientmap.h
@@ -174,6 +174,9 @@ private:
DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
{}
+
+ scene::IMeshBuffer* getBuffer();
+ void draw(video::IVideoDriver* driver);
};
Client *m_client;
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index ec1fd1c2a..eeeb19ac5 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -920,12 +920,8 @@ void GenericCAO::setNodeLight(const video::SColor &light_color)
if (m_prop.visual == "upright_sprite") {
if (!m_meshnode)
return;
-
- scene::IMesh *mesh = m_meshnode->getMesh();
- for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- buf->getMaterial().EmissiveColor = light_color;
- }
+ for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i)
+ m_meshnode->getMaterial(i).EmissiveColor = light_color;
} else {
scene::ISceneNode *node = getSceneNode();
if (!node)
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index f0d43ec7e..9e82fc3e4 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -1204,15 +1204,16 @@ void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output
void PartialMeshBuffer::beforeDraw() const
{
// Patch the indexes in the mesh buffer before draw
-
- m_buffer->Indices.clear();
- if (!m_vertex_indexes.empty()) {
- for (auto index : m_vertex_indexes)
- m_buffer->Indices.push_back(index);
- }
+ m_buffer->Indices = std::move(m_vertex_indexes);
m_buffer->setDirty(scene::EBT_INDEX);
}
+void PartialMeshBuffer::afterDraw() const
+{
+ // Take the data back
+ m_vertex_indexes = std::move(m_buffer->Indices.steal());
+}
+
/*
MapBlockMesh
*/
@@ -1582,7 +1583,7 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
const auto &t = m_transparent_triangles[i];
if (current_buffer != t.buffer) {
if (current_buffer) {
- m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
current_strain.clear();
}
current_buffer = t.buffer;
@@ -1593,7 +1594,7 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
}
if (!current_strain.empty())
- m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
}
void MapBlockMesh::consolidateTransparentBuffers()
@@ -1607,7 +1608,7 @@ void MapBlockMesh::consolidateTransparentBuffers()
for (const auto &t : m_transparent_triangles) {
if (current_buffer != t.buffer) {
if (current_buffer != nullptr) {
- this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
current_strain.clear();
}
current_buffer = t.buffer;
@@ -1618,7 +1619,7 @@ void MapBlockMesh::consolidateTransparentBuffers()
}
if (!current_strain.empty()) {
- this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
}
}
diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h
index 5e2d70b75..8133627b1 100644
--- a/src/client/mapblock_mesh.h
+++ b/src/client/mapblock_mesh.h
@@ -140,20 +140,31 @@ private:
s32 root = -1; // index of the root node
};
+/*
+ * PartialMeshBuffer
+ *
+ * Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different
+ * indices with the same vertex buffer.
+ *
+ * Irrlicht does not currently support this: `CMeshBuffer` ties together a single vertex buffer
+ * and a single index buffer. There's no way to share these between mesh buffers.
+ *
+ */
class PartialMeshBuffer
{
public:
- PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) :
- m_buffer(buffer), m_vertex_indexes(vertex_indexes)
+ PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector<u16> &&vertex_indexes) :
+ m_buffer(buffer), m_vertex_indexes(std::move(vertex_indexes))
{}
scene::IMeshBuffer *getBuffer() const { return m_buffer; }
const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
void beforeDraw() const;
+ void afterDraw() const;
private:
scene::SMeshBuffer *m_buffer;
- std::vector<u16> m_vertex_indexes;
+ mutable std::vector<u16> m_vertex_indexes;
};
/*
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index bbb872761..009a4b3d7 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -771,6 +771,8 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
}
+ shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics
+
std::string common_header = shaders_header.str();
std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp
index 07dc6daf2..c13cfe252 100644
--- a/src/client/shadows/dynamicshadowsrender.cpp
+++ b/src/client/shadows/dynamicshadowsrender.cpp
@@ -670,6 +670,7 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
std::string prefix;
if (m_shadow_map_colored)
prefix.append("#define COLORED_SHADOWS 1\n");
+ prefix.append("#line 0\n");
std::string content;
fs::ReadFile(path, content);
diff --git a/src/content/content.cpp b/src/content/content.cpp
index 66ef83d42..e576943ff 100644
--- a/src/content/content.cpp
+++ b/src/content/content.cpp
@@ -96,7 +96,12 @@ void parseContentInfo(ContentSpec &spec)
Settings conf;
if (!conf_path.empty() && conf.readConfigFile(conf_path.c_str())) {
- if (conf.exists("name"))
+ if (conf.exists("title"))
+ spec.title = conf.get("title");
+ else if (spec.type == "game" && conf.exists("name"))
+ spec.title = conf.get("name");
+
+ if (spec.type != "game" && conf.exists("name"))
spec.name = conf.get("name");
if (conf.exists("description"))
diff --git a/src/content/content.h b/src/content/content.h
index e246ed411..ce09a2eb9 100644
--- a/src/content/content.h
+++ b/src/content/content.h
@@ -27,7 +27,14 @@ struct ContentSpec
std::string type;
std::string author;
u32 release = 0;
+
+ /// Technical name / Id
std::string name;
+
+ /// Human-readable title
+ std::string title;
+
+ /// Short description
std::string desc;
std::string path;
};
diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
index 23355990e..d0de926ef 100644
--- a/src/content/subgames.cpp
+++ b/src/content/subgames.cpp
@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <common/c_internal.h>
#include "content/subgames.h"
#include "porting.h"
#include "filesys.h"
@@ -45,6 +46,25 @@ bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
}
+
+void SubgameSpec::checkAndLog() const
+{
+ // Log deprecation messages
+ auto handling_mode = get_deprecated_handling_mode();
+ if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
+ std::ostringstream os;
+ os << "Game " << title << " at " << path << ":" << std::endl;
+ for (auto msg : deprecation_msgs)
+ os << "\t" << msg << std::endl;
+
+ if (handling_mode == DeprecatedHandlingMode::Error)
+ throw ModError(os.str());
+ else
+ warningstream << os.str();
+ }
+}
+
+
struct GameFindPath
{
std::string path;
@@ -121,11 +141,13 @@ SubgameSpec findSubgame(const std::string &id)
Settings conf;
conf.readConfigFile(conf_path.c_str());
- std::string game_name;
- if (conf.exists("name"))
- game_name = conf.get("name");
+ std::string game_title;
+ if (conf.exists("title"))
+ game_title = conf.get("title");
+ else if (conf.exists("name"))
+ game_title = conf.get("name");
else
- game_name = id;
+ game_title = id;
std::string game_author;
if (conf.exists("author"))
@@ -140,8 +162,14 @@ SubgameSpec findSubgame(const std::string &id)
menuicon_path = getImagePath(
game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
#endif
- return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
+
+ SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
menuicon_path, game_author, game_release);
+
+ if (conf.exists("name") && !conf.exists("title"))
+ spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");
+
+ return spec;
}
SubgameSpec findWorldSubgame(const std::string &world_path)
@@ -159,10 +187,12 @@ SubgameSpec findWorldSubgame(const std::string &world_path)
std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
conf.readConfigFile(conf_path.c_str());
- if (conf.exists("name"))
- gamespec.name = conf.get("name");
+ if (conf.exists("title"))
+ gamespec.title = conf.get("title");
+ else if (conf.exists("name"))
+ gamespec.title = conf.get("name");
else
- gamespec.name = world_gameid;
+ gamespec.title = world_gameid;
return gamespec;
}
diff --git a/src/content/subgames.h b/src/content/subgames.h
index d36b4952f..d5d168243 100644
--- a/src/content/subgames.h
+++ b/src/content/subgames.h
@@ -29,7 +29,7 @@ class Settings;
struct SubgameSpec
{
std::string id;
- std::string name;
+ std::string title;
std::string author;
int release;
std::string path;
@@ -41,20 +41,24 @@ struct SubgameSpec
std::unordered_map<std::string, std::string> addon_mods_paths;
std::string menuicon_path;
+ // For logging purposes
+ std::vector<const char *> deprecation_msgs;
+
SubgameSpec(const std::string &id = "", const std::string &path = "",
const std::string &gamemods_path = "",
const std::unordered_map<std::string, std::string> &addon_mods_paths = {},
- const std::string &name = "",
+ const std::string &title = "",
const std::string &menuicon_path = "",
const std::string &author = "", int release = 0) :
id(id),
- name(name), author(author), release(release), path(path),
+ title(title), author(author), release(release), path(path),
gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths),
menuicon_path(menuicon_path)
{
}
bool isValid() const { return (!id.empty() && !path.empty()); }
+ void checkAndLog() const;
};
SubgameSpec findSubgame(const std::string &id);
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index ef2f8724d..9224d99ed 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -344,7 +344,7 @@ void set_default_settings()
settings->setDefault("shadow_filters", "1");
settings->setDefault("shadow_poisson_filter", "true");
settings->setDefault("shadow_update_frames", "8");
- settings->setDefault("shadow_soft_radius", "1.0");
+ settings->setDefault("shadow_soft_radius", "5.0");
settings->setDefault("shadow_sky_body_orbit_tilt", "0.0");
// Input
diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp
index b8887a4af..c5aa9c85e 100644
--- a/src/gui/guiConfirmRegistration.cpp
+++ b/src/gui/guiConfirmRegistration.cpp
@@ -54,25 +54,10 @@ GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env,
#endif
}
-GUIConfirmRegistration::~GUIConfirmRegistration()
-{
- removeChildren();
-}
-
-void GUIConfirmRegistration::removeChildren()
-{
- const core::list<gui::IGUIElement *> &children = getChildren();
- core::list<gui::IGUIElement *> children_copy;
- for (gui::IGUIElement *i : children)
- children_copy.push_back(i);
- for (gui::IGUIElement *i : children_copy)
- i->remove();
-}
-
void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
{
acceptInput();
- removeChildren();
+ removeAllChildren();
/*
Calculate new sizes and positions
diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h
index d8387201d..fb2157756 100644
--- a/src/gui/guiConfirmRegistration.h
+++ b/src/gui/guiConfirmRegistration.h
@@ -34,9 +34,6 @@ public:
s32 id, IMenuManager *menumgr, Client *client,
const std::string &playername, const std::string &password,
bool *aborted, ISimpleTextureSource *tsrc);
- ~GUIConfirmRegistration();
-
- void removeChildren();
/*
Remove and re-add (or reposition) stuff
*/
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
index f3570ccaf..422d6da16 100644
--- a/src/gui/guiFormSpecMenu.cpp
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -127,7 +127,8 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
GUIFormSpecMenu::~GUIFormSpecMenu()
{
- removeChildren();
+ removeAllChildren();
+ removeTooltip();
for (auto &table_it : m_tables)
table_it.second->drop();
@@ -174,14 +175,8 @@ void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client,
}
}
-void GUIFormSpecMenu::removeChildren()
+void GUIFormSpecMenu::removeTooltip()
{
- const core::list<gui::IGUIElement*> &children = getChildren();
-
- while (!children.empty()) {
- (*children.getLast())->remove();
- }
-
if (m_tooltip_element) {
m_tooltip_element->remove();
m_tooltip_element->drop();
@@ -199,16 +194,7 @@ void GUIFormSpecMenu::setInitialFocus()
// 5. first focusable (not statictext, not tabheader)
// 6. first child element
- core::list<gui::IGUIElement*> children = getChildren();
-
- // in case "children" contains any NULL elements, remove them
- for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
- it != children.end();) {
- if (*it)
- ++it;
- else
- it = children.erase(it);
- }
+ const auto& children = getChildren();
// 1. first empty editbox
for (gui::IGUIElement *it : children) {
@@ -236,8 +222,7 @@ void GUIFormSpecMenu::setInitialFocus()
}
// 4. last button
- for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
- it != children.end(); --it) {
+ for (auto it = children.rbegin(); it != children.rend(); ++it) {
if ((*it)->getType() == gui::EGUIET_BUTTON) {
Environment->setFocus(*it);
return;
@@ -257,7 +242,7 @@ void GUIFormSpecMenu::setInitialFocus()
if (children.empty())
Environment->setFocus(this);
else
- Environment->setFocus(*(children.begin()));
+ Environment->setFocus(children.front());
}
GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
@@ -3045,7 +3030,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
}
// Remove children
- removeChildren();
+ removeAllChildren();
+ removeTooltip();
for (auto &table_it : m_tables)
table_it.second->drop();
@@ -3341,7 +3327,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
pos_offset = v2f32();
// used for formspec versions < 3
- core::list<IGUIElement *>::Iterator legacy_sort_start = Children.getLast();
+ std::list<IGUIElement *>::iterator legacy_sort_start = std::prev(Children.end()); // last element
if (enable_prepends) {
// Backup the coordinates so that prepends can use the coordinates of choice.
@@ -3356,7 +3342,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
// legacy sorting for formspec versions < 3
if (m_formspec_version >= 3)
// prepends do not need to be reordered
- legacy_sort_start = Children.getLast();
+ legacy_sort_start = std::prev(Children.end()); // last element
else if (version_backup >= 3)
// only prepends elements have to be reordered
legacySortElements(legacy_sort_start);
@@ -3437,7 +3423,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
}
}
-void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from)
+void GUIFormSpecMenu::legacySortElements(std::list<IGUIElement *>::iterator from)
{
/*
Draw order for formspec_version <= 2:
@@ -3454,17 +3440,16 @@ void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator fro
if (from == Children.end())
from = Children.begin();
else
- from++;
+ ++from;
- core::list<IGUIElement *>::Iterator to = Children.end();
+ std::list<IGUIElement *>::iterator to = Children.end();
// 1: Copy into a sortable container
- std::vector<IGUIElement *> elements;
- for (auto it = from; it != to; ++it)
- elements.emplace_back(*it);
+ std::vector<IGUIElement *> elements(from, to);
// 2: Sort the container
std::stable_sort(elements.begin(), elements.end(),
[this] (const IGUIElement *a, const IGUIElement *b) -> bool {
+ // TODO: getSpecByID is a linear search. It should made O(1), or cached here.
const FieldSpec *spec_a = getSpecByID(a->getID());
const FieldSpec *spec_b = getSpecByID(b->getID());
return spec_a && spec_b &&
@@ -3472,10 +3457,7 @@ void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator fro
});
// 3: Re-assign the pointers
- for (auto e : elements) {
- *from = e;
- from++;
- }
+ reorderChildren(from, to, elements);
}
#ifdef __ANDROID__
@@ -3600,12 +3582,11 @@ void GUIFormSpecMenu::drawMenu()
/*
This is where all the drawing happens.
*/
- core::list<IGUIElement*>::Iterator it = Children.begin();
- for (; it != Children.end(); ++it)
- if ((*it)->isNotClipped() ||
+ for (auto child : Children)
+ if (child->isNotClipped() ||
AbsoluteClippingRect.isRectCollided(
- (*it)->getAbsolutePosition()))
- (*it)->draw();
+ child->getAbsolutePosition()))
+ child->draw();
for (gui::IGUIElement *e : m_clickthrough_elements)
e->setVisible(false);
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
index 3fedb3b78..a584456db 100644
--- a/src/gui/guiFormSpecMenu.h
+++ b/src/gui/guiFormSpecMenu.h
@@ -212,7 +212,7 @@ public:
m_lockscreensize = basescreensize;
}
- void removeChildren();
+ void removeTooltip();
void setInitialFocus();
void setFocus(const std::string &elementname)
@@ -467,7 +467,7 @@ private:
* types were drawn before others.
* This function sorts the elements in the old order for backwards compatibility.
*/
- void legacySortElements(core::list<IGUIElement *>::Iterator from);
+ void legacySortElements(std::list<IGUIElement *>::iterator from);
int m_btn_height;
gui::IGUIFont *m_font = nullptr;
diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp
index 249892950..155865472 100644
--- a/src/gui/guiKeyChangeMenu.cpp
+++ b/src/gui/guiKeyChangeMenu.cpp
@@ -103,7 +103,8 @@ GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
GUIKeyChangeMenu::~GUIKeyChangeMenu()
{
- removeChildren();
+ removeAllChildren();
+ key_used_text = nullptr;
for (key_setting *ks : key_settings) {
delete[] ks->button_name;
@@ -112,23 +113,10 @@ GUIKeyChangeMenu::~GUIKeyChangeMenu()
key_settings.clear();
}
-void GUIKeyChangeMenu::removeChildren()
-{
- const core::list<gui::IGUIElement*> &children = getChildren();
- core::list<gui::IGUIElement*> children_copy;
- for (gui::IGUIElement*i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
- key_used_text = nullptr;
-}
-
void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
{
- removeChildren();
+ removeAllChildren();
+ key_used_text = nullptr;
const float s = m_gui_scale;
DesiredRect = core::rect<s32>(
diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h
index 1c0f40247..84a898774 100644
--- a/src/gui/guiKeyChangeMenu.h
+++ b/src/gui/guiKeyChangeMenu.h
@@ -46,7 +46,6 @@ public:
IMenuManager *menumgr, ISimpleTextureSource *tsrc);
~GUIKeyChangeMenu();
- void removeChildren();
/*
Remove and re-add (or reposition) stuff
*/
diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp
index c983260f6..c39df176b 100644
--- a/src/gui/guiPasswordChange.cpp
+++ b/src/gui/guiPasswordChange.cpp
@@ -51,23 +51,6 @@ GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
{
}
-GUIPasswordChange::~GUIPasswordChange()
-{
- removeChildren();
-}
-
-void GUIPasswordChange::removeChildren()
-{
- const core::list<gui::IGUIElement *> &children = getChildren();
- core::list<gui::IGUIElement *> children_copy;
- for (gui::IGUIElement *i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
-}
void GUIPasswordChange::regenerateGui(v2u32 screensize)
{
/*
@@ -78,7 +61,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize)
/*
Remove stuff
*/
- removeChildren();
+ removeAllChildren();
/*
Calculate new sizes and positions
diff --git a/src/gui/guiPasswordChange.h b/src/gui/guiPasswordChange.h
index 7141100c0..452702add 100644
--- a/src/gui/guiPasswordChange.h
+++ b/src/gui/guiPasswordChange.h
@@ -31,9 +31,7 @@ public:
GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
IMenuManager *menumgr, Client *client,
ISimpleTextureSource *tsrc);
- ~GUIPasswordChange();
- void removeChildren();
/*
Remove and re-add (or reposition) stuff
*/
diff --git a/src/gui/guiPathSelectMenu.cpp b/src/gui/guiPathSelectMenu.cpp
index 489927a11..9c63e06b5 100644
--- a/src/gui/guiPathSelectMenu.cpp
+++ b/src/gui/guiPathSelectMenu.cpp
@@ -32,13 +32,12 @@ GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env,
GUIFileSelectMenu::~GUIFileSelectMenu()
{
- removeChildren();
setlocale(LC_NUMERIC, "C");
}
void GUIFileSelectMenu::regenerateGui(v2u32 screensize)
{
- removeChildren();
+ removeAllChildren();
m_fileOpenDialog = 0;
core::dimension2du size(600 * m_gui_scale, 400 * m_gui_scale);
diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp
index 0fe4c41bd..2d71f3453 100644
--- a/src/gui/guiScrollContainer.cpp
+++ b/src/gui/guiScrollContainer.cpp
@@ -59,12 +59,11 @@ bool GUIScrollContainer::OnEvent(const SEvent &event)
void GUIScrollContainer::draw()
{
if (isVisible()) {
- core::list<IGUIElement *>::Iterator it = Children.begin();
- for (; it != Children.end(); ++it)
- if ((*it)->isNotClipped() ||
+ for (auto child : Children)
+ if (child->isNotClipped() ||
AbsoluteClippingRect.isRectCollided(
- (*it)->getAbsolutePosition()))
- (*it)->draw();
+ child->getAbsolutePosition()))
+ child->draw();
}
}
diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp
index 61ab758a1..0f6f43fe9 100644
--- a/src/gui/guiVolumeChange.cpp
+++ b/src/gui/guiVolumeChange.cpp
@@ -45,32 +45,12 @@ GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
{
}
-GUIVolumeChange::~GUIVolumeChange()
-{
- removeChildren();
-}
-
-void GUIVolumeChange::removeChildren()
-{
- if (gui::IGUIElement *e = getElementFromId(ID_soundText))
- e->remove();
-
- if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton))
- e->remove();
-
- if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
- e->remove();
-
- if (gui::IGUIElement *e = getElementFromId(ID_soundMuteButton))
- e->remove();
-}
-
void GUIVolumeChange::regenerateGui(v2u32 screensize)
{
/*
Remove stuff
*/
- removeChildren();
+ removeAllChildren();
/*
Calculate new sizes and positions
*/
diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h
index 466e17f9d..ccdaca00b 100644
--- a/src/gui/guiVolumeChange.h
+++ b/src/gui/guiVolumeChange.h
@@ -31,9 +31,6 @@ public:
GUIVolumeChange(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr, ISimpleTextureSource *tsrc);
- ~GUIVolumeChange();
-
- void removeChildren();
/*
Remove and re-add (or reposition) stuff
*/
diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h
index 102492255..76d357340 100644
--- a/src/gui/mainmenumanager.h
+++ b/src/gui/mainmenumanager.h
@@ -64,10 +64,6 @@ public:
// Remove all entries if there are duplicates
m_stack.remove(menu);
- /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
- assert(*i == menu);
- m_stack.erase(i);*/
-
if(!m_stack.empty())
m_stack.back()->setVisible(true);
}
diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp
index 56a5d2cb9..d27f63d94 100644
--- a/src/gui/modalMenu.cpp
+++ b/src/gui/modalMenu.cpp
@@ -108,19 +108,6 @@ void GUIModalMenu::quitMenu()
#endif
}
-void GUIModalMenu::removeChildren()
-{
- const core::list<gui::IGUIElement *> &children = getChildren();
- core::list<gui::IGUIElement *> children_copy;
- for (gui::IGUIElement *i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
-}
-
// clang-format off
bool GUIModalMenu::DoubleClickDetection(const SEvent &event)
{
diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h
index 06e78f06b..e37c41533 100644
--- a/src/gui/modalMenu.h
+++ b/src/gui/modalMenu.h
@@ -47,7 +47,6 @@ public:
bool canTakeFocus(gui::IGUIElement *e);
void draw();
void quitMenu();
- void removeChildren();
virtual void regenerateGui(v2u32 screensize) = 0;
virtual void drawMenu() = 0;
diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp
index fe86adae6..0f2572ee0 100644
--- a/src/irrlicht_changes/CGUITTFont.cpp
+++ b/src/irrlicht_changes/CGUITTFont.cpp
@@ -292,9 +292,6 @@ shadow_offset(0), shadow_alpha(0), fallback(0)
Driver->grab();
setInvisibleCharacters(L" ");
-
- // Glyphs aren't reference counted, so don't try to delete them when we free the array.
- Glyphs.set_free_when_destroyed(false);
}
bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency)
@@ -411,8 +408,7 @@ CGUITTFont::~CGUITTFont()
{
// Delete the glyphs and glyph pages.
reset_images();
- CGUITTAssistDelete::Delete(Glyphs);
- //Glyphs.clear();
+ Glyphs.clear();
// We aren't using this face anymore.
auto n = c_faces.find(filename);
@@ -675,6 +671,8 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
update_glyph_pages();
auto it = Render_Map.begin();
auto ie = Render_Map.end();
+ core::array<core::vector2di> tmp_positions;
+ core::array<core::recti> tmp_source_rects;
while (it != ie)
{
CGUITTGlyphPage* page = it->second;
@@ -696,10 +694,8 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
do
++i;
while (i < page->render_positions.size() && page->render_colors[i] == colprev);
- core::array<core::vector2di> tmp_positions;
- core::array<core::recti> tmp_source_rects;
- tmp_positions.set_pointer(&page->render_positions[ibegin], i - ibegin, false, false); // no copy
- tmp_source_rects.set_pointer(&page->render_source_rects[ibegin], i - ibegin, false, false);
+ tmp_positions.set_data(&page->render_positions[ibegin], i - ibegin);
+ tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin);
--i;
if (!use_transparency)
diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h
index 9457e5b18..210222ed4 100644
--- a/src/irrlicht_changes/CGUITTFont.h
+++ b/src/irrlicht_changes/CGUITTFont.h
@@ -37,6 +37,7 @@
#include <map>
#include <irrUString.h>
#include "util/enriched_string.h"
+#include "util/basic_macros.h"
#include FT_FREETYPE_H
namespace irr
@@ -46,23 +47,34 @@ namespace gui
struct SGUITTFace;
class CGUITTFont;
- //! Class to assist in deleting glyphs.
- class CGUITTAssistDelete
- {
- public:
- template <class T, typename TAlloc>
- static void Delete(core::array<T, TAlloc>& a)
- {
- TAlloc allocator;
- allocator.deallocate(a.pointer());
- }
- };
-
//! Structure representing a single TrueType glyph.
struct SGUITTGlyph
{
//! Constructor.
- SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {}
+ SGUITTGlyph() :
+ isLoaded(false),
+ glyph_page(0),
+ source_rect(),
+ offset(),
+ advance(),
+ surface(0),
+ parent(0)
+ {}
+
+ DISABLE_CLASS_COPY(SGUITTGlyph);
+
+ //! This class would be trivially copyable except for the reference count on `surface`.
+ SGUITTGlyph(SGUITTGlyph &&other) :
+ isLoaded(other.isLoaded),
+ glyph_page(other.glyph_page),
+ source_rect(other.source_rect),
+ offset(other.offset),
+ advance(other.advance),
+ surface(other.surface),
+ parent(other.parent)
+ {
+ other.surface = 0;
+ }
//! Destructor.
~SGUITTGlyph() { unload(); }
diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
index 2b46a4d51..411250a0b 100644
--- a/src/script/lua_api/l_mainmenu.cpp
+++ b/src/script/lua_api/l_mainmenu.cpp
@@ -304,7 +304,11 @@ int ModApiMainMenu::l_get_games(lua_State *L)
lua_settable(L, top_lvl2);
lua_pushstring(L, "name");
- lua_pushstring(L, game.name.c_str());
+ lua_pushstring(L, game.title.c_str());
+ lua_settable(L, top_lvl2);
+
+ lua_pushstring(L, "title");
+ lua_pushstring(L, game.title.c_str());
lua_settable(L, top_lvl2);
lua_pushstring(L, "author");
@@ -356,6 +360,11 @@ int ModApiMainMenu::l_get_content_info(lua_State *L)
lua_pushstring(L, spec.author.c_str());
lua_setfield(L, -2, "author");
+ if (!spec.title.empty()) {
+ lua_pushstring(L, spec.title.c_str());
+ lua_setfield(L, -2, "title");
+ }
+
lua_pushinteger(L, spec.release);
lua_setfield(L, -2, "release");
diff --git a/src/server.cpp b/src/server.cpp
index d93f300d2..b6330c96a 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -440,6 +440,7 @@ void Server::init()
m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
+ m_gamespec.checkAndLog();
m_modmgr->loadMods(m_script);
// Read Textures and calculate sha1 sums
@@ -3109,7 +3110,7 @@ std::string Server::getStatusString()
// Version
os << "version: " << g_version_string;
// Game
- os << " | game: " << (m_gamespec.name.empty() ? m_gamespec.id : m_gamespec.name);
+ os << " | game: " << (m_gamespec.title.empty() ? m_gamespec.id : m_gamespec.title);
// Uptime
os << " | uptime: " << duration_to_string((int) m_uptime_counter->get());
// Max lag estimate
diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp
index e8957afd0..e5a4fac5b 100644
--- a/src/unittest/test.cpp
+++ b/src/unittest/test.cpp
@@ -362,7 +362,7 @@ struct TestMapBlock: public TestBase
MapNode node;
bool position_valid;
- core::list<v3s16> validity_exceptions;
+ std::list<v3s16> validity_exceptions;
TC()
{
@@ -373,7 +373,7 @@ struct TestMapBlock: public TestBase
{
//return position_valid ^ (p==position_valid_exception);
bool exception = false;
- for(core::list<v3s16>::Iterator i=validity_exceptions.begin();
+ for(std::list<v3s16>::iterator i=validity_exceptions.begin();
i != validity_exceptions.end(); i++)
{
if(p == *i)
diff --git a/src/unittest/test.h b/src/unittest/test.h
index 1102f6d33..79ea09471 100644
--- a/src/unittest/test.h
+++ b/src/unittest/test.h
@@ -80,7 +80,7 @@ class TestFailedException : public std::exception {
<< #expected << std::endl \
<< " at " << fs::GetFilenameFromPath(__FILE__) << ":" \
<< __LINE__ << std::endl \
- << " actual: " << a << std::endl << " expected: " \
+ << " actual : " << a << std::endl << " expected: " \
<< e << std::endl; \
throw TestFailedException(); \
} \
diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp
index 660d77d02..ff6b57507 100644
--- a/src/unittest/test_serialization.cpp
+++ b/src/unittest/test_serialization.cpp
@@ -169,23 +169,50 @@ void TestSerialization::testDeSerializeLongString()
void TestSerialization::testSerializeJsonString()
{
+ std::istringstream is(std::ios::binary);
+ const auto reset_is = [&] (const std::string &s) {
+ is.clear();
+ is.str(s);
+ };
+ const auto assert_at_eof = [] (std::istream &is) {
+ is.get();
+ UASSERT(is.eof());
+ };
+
// Test blank string
- UASSERT(serializeJsonString("") == "\"\"");
+ UASSERTEQ(std::string, serializeJsonString(""), "\"\"");
+ reset_is("\"\"");
+ UASSERTEQ(std::string, deSerializeJsonString(is), "");
+ assert_at_eof(is);
// Test basic string
- UASSERT(serializeJsonString("Hello world!") == "\"Hello world!\"");
+ UASSERTEQ(std::string, serializeJsonString("Hello world!"), "\"Hello world!\"");
+ reset_is("\"Hello world!\"");
+ UASSERTEQ(std::string, deSerializeJsonString(is), "Hello world!");
+ assert_at_eof(is);
+
+ // Test optional serialization
+ const std::pair<const char*, const char*> test_pairs[] = {
+ { "abc", "abc" },
+ { "x y z", "\"x y z\"" },
+ { "\"", "\"\\\"\"" },
+ };
+ for (auto it : test_pairs) {
+ UASSERTEQ(std::string, serializeJsonStringIfNeeded(it.first), it.second);
+ reset_is(it.second);
+ UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), it.first);
+ assert_at_eof(is);
+ }
- // MSVC fails when directly using "\\\\"
- std::string backslash = "\\";
- UASSERT(serializeJsonString(teststring2) ==
- mkstr("\"") +
+ // Test all byte values
+ const std::string bs = "\\"; // MSVC fails when directly using "\\\\"
+ const std::string expected = mkstr("\"") +
"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" +
"\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f" +
"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" +
"\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" +
- " !\\\"" + teststring2.substr(0x23, 0x2f-0x23) +
- "\\/" + teststring2.substr(0x30, 0x5c-0x30) +
- backslash + backslash + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" +
+ " !\\\"" + teststring2.substr(0x23, 0x5c-0x23) +
+ bs + bs + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" +
"\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" +
"\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f" +
"\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" +
@@ -202,14 +229,31 @@ void TestSerialization::testSerializeJsonString()
"\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" +
"\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6\\u00f7" +
"\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" +
- "\"");
-
- // Test deserialize
- std::istringstream is(serializeJsonString(teststring2), std::ios::binary);
- UASSERT(deSerializeJsonString(is) == teststring2);
- UASSERT(!is.eof());
- is.get();
- UASSERT(is.eof());
+ "\"";
+ std::string serialized = serializeJsonString(teststring2);
+ UASSERTEQ(std::string, serialized, expected);
+
+ reset_is(serialized);
+ UASSERTEQ(std::string, deSerializeJsonString(is), teststring2);
+ UASSERT(!is.eof()); // should have stopped at " so eof must not be set yet
+ assert_at_eof(is);
+
+ // Test that deserialization leaves rest of stream alone
+ std::string tmp;
+ reset_is("\"foo\"bar");
+ UASSERTEQ(std::string, deSerializeJsonString(is), "foo");
+ std::getline(is, tmp, '\0');
+ UASSERTEQ(std::string, tmp, "bar");
+
+ reset_is("\"x y z\"bar");
+ UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), "x y z");
+ std::getline(is, tmp, '\0');
+ UASSERTEQ(std::string, tmp, "bar");
+
+ reset_is("foo bar");
+ UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), "foo");
+ std::getline(is, tmp, '\0');
+ UASSERTEQ(std::string, tmp, " bar");
}
diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp
index 281061229..ee46fd941 100644
--- a/src/util/serialize.cpp
+++ b/src/util/serialize.cpp
@@ -18,15 +18,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "serialize.h"
-#include "pointer.h"
#include "porting.h"
#include "util/string.h"
+#include "util/hex.h"
#include "exceptions.h"
#include "irrlichttypes.h"
-#include <sstream>
-#include <iomanip>
-#include <vector>
+#include <iostream>
+#include <cassert>
FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN;
@@ -120,121 +119,148 @@ std::string deSerializeString32(std::istream &is)
}
////
-//// JSON
+//// JSON-like strings
////
std::string serializeJsonString(const std::string &plain)
{
- std::ostringstream os(std::ios::binary);
- os << "\"";
+ std::string tmp;
+
+ tmp.reserve(plain.size() + 2);
+ tmp.push_back('"');
for (char c : plain) {
switch (c) {
case '"':
- os << "\\\"";
+ tmp.append("\\\"");
break;
case '\\':
- os << "\\\\";
- break;
- case '/':
- os << "\\/";
+ tmp.append("\\\\");
break;
case '\b':
- os << "\\b";
+ tmp.append("\\b");
break;
case '\f':
- os << "\\f";
+ tmp.append("\\f");
break;
case '\n':
- os << "\\n";
+ tmp.append("\\n");
break;
case '\r':
- os << "\\r";
+ tmp.append("\\r");
break;
case '\t':
- os << "\\t";
+ tmp.append("\\t");
break;
default: {
if (c >= 32 && c <= 126) {
- os << c;
+ tmp.push_back(c);
} else {
- u32 cnum = (u8)c;
- os << "\\u" << std::hex << std::setw(4)
- << std::setfill('0') << cnum;
+ // We pretend that Unicode codepoints map to bytes (they don't)
+ u8 cnum = static_cast<u8>(c);
+ tmp.append("\\u00");
+ tmp.push_back(hex_chars[cnum >> 4]);
+ tmp.push_back(hex_chars[cnum & 0xf]);
}
break;
}
}
}
- os << "\"";
- return os.str();
+ tmp.push_back('"');
+ return tmp;
+}
+
+static void deSerializeJsonString(std::string &s)
+{
+ assert(s.size() >= 2);
+ assert(s.front() == '"' && s.back() == '"');
+
+ size_t w = 0; // write index
+ size_t i = 1; // read index
+ const size_t len = s.size() - 1; // string length with trailing quote removed
+
+ while (i < len) {
+ char c = s[i++];
+ assert(c != '"');
+
+ if (c != '\\') {
+ s[w++] = c;
+ continue;
+ }
+
+ if (i >= len)
+ throw SerializationError("JSON string ended prematurely");
+ char c2 = s[i++];
+ switch (c2) {
+ case 'b':
+ s[w++] = '\b';
+ break;
+ case 'f':
+ s[w++] = '\f';
+ break;
+ case 'n':
+ s[w++] = '\n';
+ break;
+ case 'r':
+ s[w++] = '\r';
+ break;
+ case 't':
+ s[w++] = '\t';
+ break;
+ case 'u': {
+ if (i + 3 >= len)
+ throw SerializationError("JSON string ended prematurely");
+ unsigned char v[4] = {};
+ for (int j = 0; j < 4; j++)
+ hex_digit_decode(s[i+j], v[j]);
+ i += 4;
+ u32 hexnumber = (v[0] << 12) | (v[1] << 8) | (v[2] << 4) | v[3];
+ // Note that this does not work for anything other than ASCII
+ // but these functions do not actually interact with real JSON input.
+ s[w++] = (int) hexnumber;
+ break;
+ }
+ default:
+ s[w++] = c2;
+ break;
+ }
+ }
+
+ assert(w <= i && i <= len);
+ // Truncate string to current write index
+ s.resize(w);
}
std::string deSerializeJsonString(std::istream &is)
{
- std::ostringstream os(std::ios::binary);
- char c, c2;
+ std::string tmp;
+ char c;
+ bool was_backslash = false;
// Parse initial doublequote
- is >> c;
+ c = is.get();
if (c != '"')
throw SerializationError("JSON string must start with doublequote");
+ tmp.push_back(c);
- // Parse characters
+ // Grab the entire json string
for (;;) {
c = is.get();
if (is.eof())
throw SerializationError("JSON string ended prematurely");
- if (c == '"') {
- return os.str();
- }
-
- if (c == '\\') {
- c2 = is.get();
- if (is.eof())
- throw SerializationError("JSON string ended prematurely");
- switch (c2) {
- case 'b':
- os << '\b';
- break;
- case 'f':
- os << '\f';
- break;
- case 'n':
- os << '\n';
- break;
- case 'r':
- os << '\r';
- break;
- case 't':
- os << '\t';
- break;
- case 'u': {
- int hexnumber;
- char hexdigits[4 + 1];
-
- is.read(hexdigits, 4);
- if (is.eof())
- throw SerializationError("JSON string ended prematurely");
- hexdigits[4] = 0;
-
- std::istringstream tmp_is(hexdigits, std::ios::binary);
- tmp_is >> std::hex >> hexnumber;
- os << (char)hexnumber;
- break;
- }
- default:
- os << c2;
- break;
- }
- } else {
- os << c;
- }
+ tmp.push_back(c);
+ if (was_backslash)
+ was_backslash = false;
+ else if (c == '\\')
+ was_backslash = true;
+ else if (c == '"')
+ break; // found end of string
}
- return os.str();
+ deSerializeJsonString(tmp);
+ return tmp;
}
std::string serializeJsonStringIfNeeded(const std::string &s)
@@ -248,41 +274,21 @@ std::string serializeJsonStringIfNeeded(const std::string &s)
std::string deSerializeJsonStringIfNeeded(std::istream &is)
{
- std::stringstream tmp_os(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
- bool expect_initial_quote = true;
- bool is_json = false;
- bool was_backslash = false;
- for (;;) {
- char c = is.get();
- if (is.eof())
- break;
-
- if (expect_initial_quote && c == '"') {
- tmp_os << c;
- is_json = true;
- } else if(is_json) {
- tmp_os << c;
- if (was_backslash)
- was_backslash = false;
- else if (c == '\\')
- was_backslash = true;
- else if (c == '"')
- break; // Found end of string
- } else {
- if (c == ' ') {
- // Found end of word
- is.unget();
- break;
- }
-
- tmp_os << c;
- }
- expect_initial_quote = false;
- }
- if (is_json) {
- return deSerializeJsonString(tmp_os);
+ // Check for initial quote
+ char c = is.peek();
+ if (is.eof())
+ return "";
+
+ if (c == '"') {
+ // json string: defer to the right implementation
+ return deSerializeJsonString(is);
}
- return tmp_os.str();
+ // not a json string:
+ std::string tmp;
+ std::getline(is, tmp, ' ');
+ if (!is.eof())
+ is.unget(); // we hit a space, put it back
+ return tmp;
}