feat: casually rewrite in C++

This commit is contained in:
LordMZTE 2022-11-05 18:07:20 +01:00
parent 120894a992
commit 162bd4f953
Signed by: LordMZTE
GPG Key ID: B64802DC33A64FF6
25 changed files with 368 additions and 255 deletions

View File

@ -1,4 +0,0 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

31
.gitignore vendored
View File

@ -1,29 +1,2 @@
# The directory Mix will write compiled artifacts to.
_build/
# If you run "mix test --cover", coverage assets end up here.
cover/
# The directory Mix downloads your dependencies sources to.
deps/
# Where third-party dependencies like ExDoc output generated docs.
doc/
# Ignore .fetch files in case you like to edit your project deps locally.
.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
portingtools-*.tar
# Temporary files, for example, from tests.
tmp/
# Language Server
.elixir_ls/
build
.cache

12
CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 2.8.11)
project(PORTINGTOOLS)
file(GLOB_RECURSE sources src/*)
find_package(Boost 1.80 COMPONENTS log filesystem REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
add_executable(portingtools ${sources})
target_link_libraries(portingtools LINK_PUBLIC ${Boost_LIBRARIES})

View File

@ -2,11 +2,12 @@
This tool is meant to make porting mods easier for MineteckReloaded.
Also, it helped me learn some elixir, so don't expect good code.
Also, it helped me learn some c++, so don't expect good code.
## Installation
- `MIX_ENV=prod mix release`
- start the server with `_build/prod/rel/portingtools/bin/portingtools start`
- map stuff with `./client.sh map`
- resolve ResourceLocations with `./client.sh resourceloc`
- `cmake -S. -Bbuild`
- `cmake --build build`
- start the server with `build/portingtools serve`
- map stuff with `build/portingtools map`
- resolve ResourceLocations with `build/portingtools resourceloc`

View File

@ -1,2 +0,0 @@
#!/usr/bin/sh
exec "$(dirname "$(realpath "$0")")"/_build/prod/rel/portingtools/bin/portingtools rpc "Cli.call(~s<$1>)"

View File

@ -1,31 +0,0 @@
defmodule Cli do
def call(command) do
case command do
"map" ->
input()
|> PortingTools.Mappings.map()
|> output(:map)
"resourceloc" ->
input()
|> PortingTools.ResourceLoc.resolve()
|> output(:map)
_ ->
raise "Invalid command!"
end
end
def input() do
IO.gets(nil) |> String.trim()
end
def output(response, type) do
case {response, type} do
{s, _} when is_binary(s) -> s
{nil, :map} -> "<unknown>"
{nil, :resourceloc} -> "<invalid>"
end
|> IO.puts()
end
end

View File

@ -1,18 +0,0 @@
defmodule PortingTools.Application do
use Application
@impl true
def start(_type, _args) do
#{:ok, _} = Node.start(:"portingtools@localhost", :shortnames)
#Node.set_cookie(Node.self(), :ptools)
children = [
PortingTools.ResourceLoc,
PortingTools.Mappings,
PortingTools.Mappings.Agent,
]
opts = [strategy: :one_for_one, name: PortingTools.Supervisor]
Supervisor.start_link(children, opts)
end
end

View File

@ -1,22 +0,0 @@
defmodule PortingTools.Mappings do
use GenServer, restart: :transient
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def init(_init_arg) do
Logger.info("Initilizing mappings server @ #{__MODULE__}")
{:ok, nil}
end
def map(str) do
GenServer.call(__MODULE__, {:map, str})
end
def handle_call({:map, str}, _from, state) do
Logger.info("Got map request to map #{str}")
{:reply, PortingTools.Mappings.Agent.map(str), state}
end
end

View File

@ -1,74 +0,0 @@
defmodule PortingTools.Mappings.Agent do
@moduledoc """
This module manages and loads the mappings
and renames.
"""
use Agent, restart: :transient
require Logger
def start_link(_) do
Agent.start_link(&init/0, name: __MODULE__)
end
defmodule PortingTools.Mappings.Agent.State do
defstruct [:mappings, :renames]
end
def init() do
%PortingTools.Mappings.Agent.State{
mappings: load_mappings(),
renames: load_renames()
}
end
def load_mappings() do
Logger.info("reading mappings")
mappings =
File.ls!("mappings")
|> Stream.flat_map(&File.stream!("mappings/#{&1}"))
|> Stream.map(&String.split(&1, ","))
|> Enum.reduce(%{}, fn [remapped, orig | _], map ->
Map.put(map, String.trim(remapped), String.trim(orig))
end)
Logger.info("read #{map_size(mappings)} mappings")
mappings
end
def load_renames() do
Logger.info("reading renames")
renames =
File.stream!("renames.csv")
|> Stream.map(&String.split(&1, ","))
|> Enum.reduce(%{}, fn [old, new | _], map ->
Map.put(map, String.trim(old), String.trim(new))
end)
Logger.info("read #{map_size(renames)} renames")
renames
end
def map(key) do
mapping = Agent.get(__MODULE__, &Map.get(&1.mappings, key))
case mapping do
nil ->
Logger.warn("tried to map unknown symbol")
nil
m ->
case Agent.get(__MODULE__, &Map.get(&1.renames, m)) do
nil ->
m
r ->
Logger.info("found rename for #{m} -> #{r}")
r
end
end
end
end

View File

@ -1,32 +0,0 @@
defmodule PortingTools.ResourceLoc do
use GenServer, restart: :transient
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def init(_init_arg) do
Logger.info("initializing Resourceloc server @ #{__MODULE__}")
{:ok, nil}
end
def resolve(str) do
GenServer.call(__MODULE__, {:resolve, str})
end
def handle_call({:resolve, str}, _from, state) do
Logger.info("got ResourceLoc resolve request")
try do
["mods", mod | rest] =
String.trim(str)
|> String.trim(<<?">>)
|> String.split("/", trim: true)
{:reply, ~s<new ResourceLocation("#{mod}", "#{Enum.join(rest, "/")}")>, state}
rescue
MatchError -> {:reply, nil, state}
end
end
end

29
mix.exs
View File

@ -1,29 +0,0 @@
defmodule PortingTools.MixProject do
use Mix.Project
def project do
[
app: :portingtools,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {PortingTools.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

View File

@ -10,3 +10,7 @@ getLastAttackingEntity,getLastAttacker
getBlockId,getBlock
markBlockForRenderUpdate,markBlockRangeForRenderUpdate
setCompoundTag,setTag
getEntityName,getCommandSenderName
textureList,framesTextureData
bucketWater,water_bucket
ingotIron,iron_ingot

1 getBlockTileEntity getTileEntity
10 getBlockId getBlock
11 markBlockForRenderUpdate markBlockRangeForRenderUpdate
12 setCompoundTag setTag
13 getEntityName getCommandSenderName
14 textureList framesTextureData
15 bucketWater water_bucket
16 ingotIron iron_ingot

69
src/client.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "data.hpp"
#include <boost/log/trivial.hpp>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <string>
#include <sys/msg.h>
namespace client {
void sendMsg(int msqid, data::RequestMsg *msg) {
if (msgsnd(msqid, msg, sizeof(data::RequestMsg) - sizeof(long), 0) < 0) {
BOOST_LOG_TRIVIAL(fatal) << "send error " << strerror(errno);
exit(1);
}
}
std::string recvMsg(int msqid) {
data::ResponseMsg msg;
if (msgrcv(msqid, &msg, sizeof(data::ResponseMsg) - sizeof(long),
data::clientbound_msg, 0) < 0) {
BOOST_LOG_TRIVIAL(fatal) << "receive error " << strerror(errno);
exit(1);
}
return std::string(&msg.data[0], msg.datalen);
}
void map(key_t key) {
std::string inp;
std::cin >> inp;
auto msqid = msgget(key, 0664);
if (msqid < 0) {
BOOST_LOG_TRIVIAL(fatal) << "msgget fail";
exit(0);
}
data::RequestMsg req;
req.msgtype = data::serverbound_msg;
req.type = data::map;
req.datalen = inp.size();
std::copy(inp.begin(), inp.end(), req.data);
sendMsg(msqid, &req);
std::cout << recvMsg(msqid) << std::endl;
}
void resolveResourceLoc(key_t key) {
std::string inp;
std::cin >> inp;
auto msqid = msgget(key, 0664);
if (msqid < 0) {
BOOST_LOG_TRIVIAL(fatal) << "msgget fail";
exit(0);
}
data::RequestMsg req;
req.msgtype = data::serverbound_msg;
req.type = data::resolve_resource_loc;
req.datalen = inp.size();
std::copy(inp.begin(), inp.end(), req.data);
sendMsg(msqid, &req);
std::cout << recvMsg(msqid) << std::endl;
}
} // namespace client

6
src/client.hpp Normal file
View File

@ -0,0 +1,6 @@
#include <sys/types.h>
namespace client {
void map(key_t key);
void resolveResourceLoc(key_t key);
} // namespace client

17
src/data.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "data.hpp"
#include <boost/log/trivial.hpp>
#include <cstdlib>
#include <sys/ipc.h>
namespace data {
key_t getIpcKeyFromExeName(char *argv0) {
auto k = ftok(argv0, 'X');
if (k < 0) {
BOOST_LOG_TRIVIAL(fatal) << "failed to get IPC key";
exit(1);
}
return k;
}
} // namespace data

29
src/data.hpp Normal file
View File

@ -0,0 +1,29 @@
#include <sys/types.h>
namespace data {
const int serverbound_msg = 1;
const int clientbound_msg = 2;
key_t getIpcKeyFromExeName(char *argv0);
enum RequestType {
map,
resolve_resource_loc,
};
struct RequestMsg {
long msgtype;
RequestType type;
unsigned int datalen;
char data[128];
};
struct ResponseMsg {
long msgtype;
unsigned int datalen;
char data[128];
};
} // namespace data

25
src/main.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "client.hpp"
#include "data.hpp"
#include "server.hpp"
#include <boost/log/trivial.hpp>
#include <cstring>
int main(int argc, char *argv[]) {
if (argc < 2) {
BOOST_LOG_TRIVIAL(fatal) << "not enough arguments!";
return 1;
}
if (strcmp(argv[1], "serve") == 0) {
server::run(data::getIpcKeyFromExeName(argv[0]));
} else if (strcmp(argv[1], "map") == 0) {
client::map(data::getIpcKeyFromExeName(argv[0]));
} else if (strcmp(argv[1], "resourceloc") == 0) {
client::resolveResourceLoc(data::getIpcKeyFromExeName(argv[0]));
} else {
BOOST_LOG_TRIVIAL(fatal) << "unknown command!";
return 1;
}
return 0;
}

67
src/mappings.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "mappings.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/log/trivial.hpp>
#include <fstream>
#include <string>
#include <vector>
namespace fs = boost::filesystem;
namespace mappings {
void loadFile(std::string path, std::map<std::string, std::string> *map) {
std::ifstream file(path);
std::string line;
while (std::getline(file, line)) {
std::vector<std::string> splits;
boost::algorithm::split(splits, line, boost::is_any_of(",\t|"));
if (splits.size() < 2) {
BOOST_LOG_TRIVIAL(warning) << "found invalid mapping '" << line << "'";
continue;
}
(*map)[splits[0]] = splits[1];
}
}
Mappings Mappings::load() {
std::map<std::string, std::string> mappings;
std::map<std::string, std::string> renames;
// load mappings
fs::directory_iterator end_itr;
for (fs::directory_iterator itr("mappings"); itr != end_itr; ++itr) {
if (fs::is_directory(itr->status()) ||
!boost::algorithm::ends_with(itr->path().string(), ".csv"))
continue;
loadFile(itr->path().string(), &mappings);
}
if (fs::is_regular_file("renames.csv")) {
loadFile("renames.csv", &renames);
}
BOOST_LOG_TRIVIAL(info) << "loaded " << mappings.size() << " mappings and "
<< renames.size() << " renames";
return {mappings, renames};
}
std::string Mappings::map(std::string inp) {
if (!this->mappings.count(inp)) {
BOOST_LOG_TRIVIAL(warning) << "unknown mapping '" << inp << "'";
return "<unknown>";
}
auto mapped = this->mappings[inp];
if (this->renames.count(mapped))
return renames[mapped];
return mapped;
}
} // namespace mappings

17
src/mappings.hpp Normal file
View File

@ -0,0 +1,17 @@
#include <map>
#include <string>
namespace mappings {
class Mappings {
std::map<std::string, std::string> mappings;
std::map<std::string, std::string> renames;
public:
Mappings(std::map<std::string, std::string> mappings,
std::map<std::string, std::string> renames) {
this->mappings = mappings;
this->renames = renames;
}
static Mappings load();
std::string map(std::string inp);
};
} // namespace mappings

34
src/resourceloc.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "resourceloc.hpp"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace resourceloc {
std::string resolve(std::string inp) {
boost::algorithm::trim_if(inp, boost::is_any_of(" \n\r\""));
std::vector<std::string> components;
boost::algorithm::split(components, inp, boost::is_any_of("/"));
if (components.size() < 3 || components[0] != "" || components[1] != "mods") {
BOOST_LOG_TRIVIAL(warning) << "invalid resourceloc";
return "<invalid>";
}
std::string path_component;
for (unsigned int i = 3; i < components.size(); i++) {
path_component += components[i];
if (i != components.size() - 1) {
path_component += "/";
}
}
return boost::str(boost::format("new ResourceLocation(\"%1%\", \"%2%\")") %
components[2] % path_component);
}
} // namespace resourceloc

4
src/resourceloc.hpp Normal file
View File

@ -0,0 +1,4 @@
#include <string>
namespace resourceloc {
std::string resolve(std::string inp);
}

71
src/server.cpp Normal file
View File

@ -0,0 +1,71 @@
#include "server.hpp"
#include "data.hpp"
#include "mappings.hpp"
#include "resourceloc.hpp"
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/ipc.h>
#include <sys/msg.h>
namespace server {
void sendToClient(int msqid, std::string msg) {
data::ResponseMsg pkt;
pkt.msgtype = data::clientbound_msg;
pkt.datalen = msg.size();
std::copy(msg.begin(), msg.end(), pkt.data);
if (msgsnd(msqid, &pkt, sizeof(data::ResponseMsg) - sizeof(long), 0) < 0) {
BOOST_LOG_TRIVIAL(error) << "failed to send response " << strerror(errno);
}
}
void handleResolveResourceLoc(int msqid, data::RequestMsg *msg) {
std::string s(&msg->data[0], msg->datalen);
BOOST_LOG_TRIVIAL(info) << "got request to resolve resourceloc '" << s << "'";
auto res = resourceloc::resolve(s);
sendToClient(msqid, res);
}
void handleMap(int msqid, mappings::Mappings *mappings, data::RequestMsg *msg) {
std::string s(&msg->data[0], msg->datalen);
BOOST_LOG_TRIVIAL(info) << "got request to map '" << s << "'";
sendToClient(msqid, mappings->map(s));
}
void run(key_t key) {
BOOST_LOG_TRIVIAL(info) << "starting server";
auto msqid = msgget(key, 0664 | IPC_CREAT);
if (msqid < 0) {
BOOST_LOG_TRIVIAL(fatal) << "msgget fail";
exit(1);
}
BOOST_LOG_TRIVIAL(info) << "msqid " << msqid;
auto mappings = mappings::Mappings::load();
data::RequestMsg msg;
for (;;) {
if (msgrcv(msqid, &msg, sizeof(data::RequestMsg) - sizeof(long),
data::serverbound_msg, 0) < 0) {
BOOST_LOG_TRIVIAL(error) << "receive error " << strerror(errno);
continue;
}
switch (msg.type) {
case data::map:
handleMap(msqid, &mappings, &msg);
break;
case data::resolve_resource_loc:
handleResolveResourceLoc(msqid, &msg);
break;
}
}
}
} // namespace server

5
src/server.hpp Normal file
View File

@ -0,0 +1,5 @@
#include <sys/types.h>
namespace server {
void run(key_t key);
}

View File

@ -1,8 +0,0 @@
defmodule PortingtoolsTest do
use ExUnit.Case
doctest Portingtools
test "greets the world" do
assert Portingtools.hello() == :world
end
end

View File

@ -1 +0,0 @@
ExUnit.start()