Compare commits
17 Commits
fcafb1bd7e
...
master
Author | SHA1 | Date | |
---|---|---|---|
161f605b20 | |||
6aa90a1852 | |||
f69923cbbb | |||
b62ab60366 | |||
117e40dc9e | |||
050c7f3be1 | |||
4c6beb592c | |||
a91b4d41dd | |||
d18ec4e1ca | |||
7c3b867c7c | |||
e1e2563ac2 | |||
6f6894419a | |||
70317ab4db | |||
4295c6cc53 | |||
06bd55c165 | |||
f275cb02d4 | |||
95552a2bf3 |
159
.github/workflows/cd.yml
vendored
Normal file
159
.github/workflows/cd.yml
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
name: ContinuousDelivery
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: RelWidthDebInfo
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
linux-ubuntu:
|
||||
timeout-minutes: 10
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build
|
||||
run: ctest -C ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Determine tag name
|
||||
id: tag
|
||||
shell: bash
|
||||
# taken from llama.cpp
|
||||
run: |
|
||||
SHORT_HASH="$(git rev-parse --short=7 HEAD)"
|
||||
if [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then
|
||||
echo "name=dev-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-')
|
||||
echo "name=dev-${SAFE_NAME}-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Compress artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
tar -czvf ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64.tar.gz -C ${{github.workspace}}/build/bin/ .
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# TODO: simpler name?
|
||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64
|
||||
# TODO: do propper packing
|
||||
path: |
|
||||
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-ubuntu20.04-x86_64.tar.gz
|
||||
|
||||
|
||||
windows:
|
||||
timeout-minutes: 15
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# setup vs env
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Determine tag name
|
||||
id: tag
|
||||
shell: bash
|
||||
# taken from llama.cpp
|
||||
run: |
|
||||
SHORT_HASH="$(git rev-parse --short=7 HEAD)"
|
||||
if [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then
|
||||
echo "name=dev-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-')
|
||||
echo "name=dev-${SAFE_NAME}-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Compress artifacts
|
||||
shell: powershell
|
||||
run: |
|
||||
Compress-Archive -Path ${{github.workspace}}/build/bin/* -Destination ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# TODO: simpler name?
|
||||
name: ${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64
|
||||
# TODO: do propper packing
|
||||
path: |
|
||||
${{github.workspace}}/${{ github.event.repository.name }}-${{ steps.tag.outputs.name }}-${{ runner.os }}-msvc-x86_64.zip
|
||||
|
||||
release:
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- linux-ubuntu
|
||||
- windows
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Determine tag name
|
||||
id: tag
|
||||
shell: bash
|
||||
# taken from llama.cpp
|
||||
run: |
|
||||
SHORT_HASH="$(git rev-parse --short=7 HEAD)"
|
||||
if [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then
|
||||
echo "name=dev-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-')
|
||||
echo "name=dev-${SAFE_NAME}-${SHORT_HASH}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Download artifacts
|
||||
id: download-artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./artifacts/
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ steps.tag.outputs.name }}
|
||||
shell: bash
|
||||
run: |
|
||||
gh release create "$tag" \
|
||||
--repo="$GITHUB_REPOSITORY" \
|
||||
--title="nightly ${tag#v}" \
|
||||
--notes="nightly build" \
|
||||
--prerelease
|
||||
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ steps.tag.outputs.name }}
|
||||
shell: bash
|
||||
run: |
|
||||
ls -laR ./artifacts
|
||||
gh release upload "$tag" ./artifacts/*/* \
|
||||
--repo="$GITHUB_REPOSITORY"
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2025 Erik Scholz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
9
external/CMakeLists.txt
vendored
9
external/CMakeLists.txt
vendored
@ -22,8 +22,10 @@ endif()
|
||||
|
||||
if (NOT TARGET filewatch)
|
||||
FetchContent_Declare(filewatch
|
||||
GIT_REPOSITORY https://github.com/ThomasMonkman/filewatch.git
|
||||
GIT_TAG master
|
||||
#GIT_REPOSITORY https://github.com/ThomasMonkman/filewatch.git
|
||||
GIT_REPOSITORY https://github.com/justinboswell/filewatch
|
||||
#GIT_TAG master
|
||||
GIT_TAG 2cef7983449579555799065f31d022b86b739d62
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
@ -37,5 +39,8 @@ if (NOT TARGET filewatch)
|
||||
)
|
||||
target_include_directories(filewatch INTERFACE ${filewatch_SOURCE_DIR})
|
||||
target_compile_features(filewatch INTERFACE cxx_std_11)
|
||||
if(APPLE)
|
||||
target_link_libraries(filewatch INTERFACE "-framework CoreServices")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
@ -3,10 +3,14 @@
|
||||
#include <entt/entt.hpp>
|
||||
#include <entt/fwd.hpp>
|
||||
|
||||
#include <solanaceae/util/config_model.hpp>
|
||||
|
||||
#include "factorio_log_parser.hpp"
|
||||
#include "factorio.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static std::unique_ptr<FactorioLogParser> g_flp = nullptr;
|
||||
static std::unique_ptr<Factorio> g_f = nullptr;
|
||||
|
||||
constexpr const char* plugin_name = "Factorio";
|
||||
@ -29,14 +33,17 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
|
||||
}
|
||||
|
||||
try {
|
||||
auto* cr = PLUG_RESOLVE_INSTANCE_VERSIONED(Contact3Registry, "1");
|
||||
auto* rmm = PLUG_RESOLVE_INSTANCE(RegistryMessageModel);
|
||||
auto* conf = PLUG_RESOLVE_INSTANCE(ConfigModelI);
|
||||
auto* cs = PLUG_RESOLVE_INSTANCE(ContactStore4I);
|
||||
auto* rmm = PLUG_RESOLVE_INSTANCE(RegistryMessageModelI);
|
||||
|
||||
// static store, could be anywhere tho
|
||||
// construct with fetched dependencies
|
||||
g_f = std::make_unique<Factorio>(*cr, *rmm);
|
||||
g_flp = std::make_unique<FactorioLogParser>(*conf);
|
||||
g_f = std::make_unique<Factorio>(*conf, *cs, *rmm, *g_flp);
|
||||
|
||||
// register types
|
||||
PLUG_PROVIDE_INSTANCE(FactorioLogParser, plugin_name, g_flp.get());
|
||||
PLUG_PROVIDE_INSTANCE(Factorio, plugin_name, g_f.get());
|
||||
} catch (const ResolveException& e) {
|
||||
std::cerr << "PLUGIN " << plugin_name << " " << e.what << "\n";
|
||||
@ -50,11 +57,11 @@ SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void) {
|
||||
std::cout << "PLUGIN " << plugin_name << " STOP()\n";
|
||||
|
||||
g_f.reset();
|
||||
g_flp.reset();
|
||||
}
|
||||
|
||||
SOLANA_PLUGIN_EXPORT float solana_plugin_tick(float) {
|
||||
//return g_rpbot->tick(delta);
|
||||
return 1000.f;
|
||||
SOLANA_PLUGIN_EXPORT float solana_plugin_tick(float delta) {
|
||||
return g_flp->tick(delta);
|
||||
}
|
||||
|
||||
} // extern C
|
||||
|
@ -5,6 +5,8 @@ project(solanaceae)
|
||||
add_library(solanaceae_factorio
|
||||
./log_parse.hpp
|
||||
./log_parse.cpp
|
||||
./factorio_log_parser.hpp
|
||||
./factorio_log_parser.cpp
|
||||
./factorio.hpp
|
||||
./factorio.cpp
|
||||
)
|
||||
|
168
src/factorio.cpp
168
src/factorio.cpp
@ -1,18 +1,61 @@
|
||||
#include "./factorio.hpp"
|
||||
|
||||
#include <solanaceae/util/config_model.hpp>
|
||||
#include <solanaceae/util/utils.hpp>
|
||||
|
||||
#include <solanaceae/contact/contact_store_i.hpp>
|
||||
|
||||
#include <solanaceae/message3/components.hpp>
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
|
||||
#include "./log_parse.hpp"
|
||||
void Factorio::sendToLinked(const std::string& message) {
|
||||
for (const auto& h : _linked_contacts) {
|
||||
if (!static_cast<bool>(h)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Factorio::Factorio(Contact3Registry& cr, RegistryMessageModel& rmm) :
|
||||
_cr(cr),
|
||||
_rmm.sendText(h, message);
|
||||
}
|
||||
}
|
||||
|
||||
Factorio::Factorio(ConfigModelI& conf, ContactStore4I& cs, RegistryMessageModelI& rmm, FactorioLogParser& flp) :
|
||||
_conf(conf),
|
||||
_cs(cs),
|
||||
_rmm(rmm),
|
||||
_fw("test.txt", [this](const auto& path, const auto event){ this->onFileEvent(path, event);})
|
||||
_rmm_sr(_rmm.newSubRef(this)),
|
||||
_flp(flp),
|
||||
_flp_sr(_flp.newSubRef(this))
|
||||
{
|
||||
// config
|
||||
for (const auto&& [contact_id, enabled] : conf.entries_bool("Factorio", "link_to_contact")) {
|
||||
//std::cout << "config id:" << contact_id << " e:" << enabled << "\n";
|
||||
|
||||
const auto id_vec = hex2bin(contact_id);
|
||||
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_construct);
|
||||
// search
|
||||
ContactHandle4 h = _cs.getOneContactByID(ByteSpan{id_vec});
|
||||
if (!static_cast<bool>(h)) {
|
||||
// not found, create thin contact with just id
|
||||
h = _cs.contactHandle(_cs.registry().create());
|
||||
h.emplace<Contact::Components::ID>(id_vec);
|
||||
_cs.throwEventConstruct(h);
|
||||
std::cout << "Factorio: contact not found, created thin contact from ID. (" << contact_id << ")\n";
|
||||
}
|
||||
_linked_contacts.push_back(h);
|
||||
}
|
||||
|
||||
_rmm_sr.subscribe(RegistryMessageModel_Event::message_construct);
|
||||
|
||||
_flp_sr
|
||||
.subscribe(FactorioLogParser_Event::join)
|
||||
.subscribe(FactorioLogParser_Event::leave)
|
||||
.subscribe(FactorioLogParser_Event::chat)
|
||||
.subscribe(FactorioLogParser_Event::died)
|
||||
.subscribe(FactorioLogParser_Event::evolution)
|
||||
.subscribe(FactorioLogParser_Event::research_started)
|
||||
.subscribe(FactorioLogParser_Event::research_finished)
|
||||
.subscribe(FactorioLogParser_Event::research_cancelled)
|
||||
;
|
||||
}
|
||||
|
||||
Factorio::~Factorio(void) {
|
||||
@ -22,7 +65,118 @@ bool Factorio::onEvent(const Message::Events::MessageConstruct& e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void onFileEvent(const std::string& path, const filewatch::Event change_type) {
|
||||
std::cout << "file even " << filewatch::event_to_string(change_type) << " on '" << path << "'\n";
|
||||
bool Factorio::onEvent(const FactorioLog::Events::Join& e) {
|
||||
std::cout << "Factorio: event join " << e.player_name << "\n";
|
||||
|
||||
if (!_conf.get_bool("Factorio", "forward", "join").value_or(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message{e.player_name};
|
||||
message += " joined";
|
||||
|
||||
sendToLinked(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::Leave& e) {
|
||||
std::cout << "Factorio: event leave " << e.player_name << " " << e.reason << "\n";
|
||||
|
||||
if (!_conf.get_bool("Factorio", "forward", "leave").value_or(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message{e.player_name};
|
||||
message += " left";
|
||||
|
||||
sendToLinked(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::Chat& e) {
|
||||
std::cout << "Factorio: event chat " << e.player_name << ": " << e.message << "\n";
|
||||
|
||||
if (!_conf.get_bool("Factorio", "forward", "chat").value_or(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore pings
|
||||
constexpr std::string_view ping_prefix{"[gps="};
|
||||
if (e.message.size() > ping_prefix.size() && e.message.substr(0, ping_prefix.size()) == ping_prefix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message{"<"};
|
||||
message += e.player_name;
|
||||
message += ">: ";
|
||||
message += e.message;
|
||||
|
||||
sendToLinked(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::Died& e) {
|
||||
std::cout << "Factorio: event died " << e.player_name << ": " << e.reason << "\n";
|
||||
|
||||
if (!_conf.get_bool("Factorio", "forward", "died").value_or(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message{e.player_name};
|
||||
message += " died from ";
|
||||
message += e.reason;
|
||||
|
||||
sendToLinked(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::Evolution& e) {
|
||||
std::cout << "Factorio: event evolution " << e.evo << "\n";
|
||||
|
||||
//if (!_conf.get_bool("Factorio", "forward", "evolution").value_or(true)) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::ResearchStarted& e) {
|
||||
std::cout << "Factorio: event research started " << e.name << "\n";
|
||||
|
||||
//if (!_conf.get_bool("Factorio", "forward", "research_started").value_or(true)) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::ResearchFinished& e) {
|
||||
std::cout << "Factorio: event research finished " << e.name << "\n";
|
||||
|
||||
if (!_conf.get_bool("Factorio", "forward", "research_finished").value_or(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message{"Research "};
|
||||
message += e.name;
|
||||
message += " finished!";
|
||||
|
||||
sendToLinked(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Factorio::onEvent(const FactorioLog::Events::ResearchCancelled& e) {
|
||||
std::cout << "Factorio: event research cancelled " << e.name << "\n";
|
||||
|
||||
//if (!_conf.get_bool("Factorio", "forward", "research_cancelled").value_or(true)) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/contact/fwd.hpp>
|
||||
#include <solanaceae/message3/registry_message_model.hpp>
|
||||
|
||||
#include <FileWatch.hpp>
|
||||
#include "./factorio_log_parser.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Factorio : public RegistryMessageModelEventI {
|
||||
Contact3Registry& _cr;
|
||||
RegistryMessageModel& _rmm;
|
||||
// fwd
|
||||
struct ConfigModelI;
|
||||
|
||||
filewatch::FileWatch<std::string> _fw;
|
||||
class Factorio : public RegistryMessageModelEventI, public FactorioLogParserEventI {
|
||||
ConfigModelI& _conf;
|
||||
ContactStore4I& _cs;
|
||||
RegistryMessageModelI& _rmm;
|
||||
RegistryMessageModelI::SubscriptionReference _rmm_sr;
|
||||
FactorioLogParser& _flp;
|
||||
FactorioLogParser::SubscriptionReference _flp_sr;
|
||||
|
||||
std::vector<ContactHandle4> _linked_contacts;
|
||||
|
||||
void sendToLinked(const std::string& message);
|
||||
|
||||
public:
|
||||
Factorio(Contact3Registry& cr, RegistryMessageModel& rmm);
|
||||
Factorio(ConfigModelI& conf, ContactStore4I& cs, RegistryMessageModelI& rmm, FactorioLogParser& flp);
|
||||
virtual ~Factorio(void);
|
||||
|
||||
protected: // rmm
|
||||
bool onEvent(const Message::Events::MessageConstruct& e) override;
|
||||
|
||||
protected:
|
||||
void onFileEvent(const std::string& path, const filewatch::Event change_type);
|
||||
protected: // flp
|
||||
bool onEvent(const FactorioLog::Events::Join&) override;
|
||||
bool onEvent(const FactorioLog::Events::Leave&) override;
|
||||
bool onEvent(const FactorioLog::Events::Chat&) override;
|
||||
bool onEvent(const FactorioLog::Events::Died&) override;
|
||||
bool onEvent(const FactorioLog::Events::Evolution&) override;
|
||||
bool onEvent(const FactorioLog::Events::ResearchStarted&) override;
|
||||
bool onEvent(const FactorioLog::Events::ResearchFinished&) override;
|
||||
bool onEvent(const FactorioLog::Events::ResearchCancelled&) override;
|
||||
};
|
||||
|
||||
|
236
src/factorio_log_parser.cpp
Normal file
236
src/factorio_log_parser.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
#include "./factorio_log_parser.hpp"
|
||||
|
||||
#include <solanaceae/util/config_model.hpp>
|
||||
|
||||
#include "./log_parse.hpp"
|
||||
|
||||
FactorioLogParser::FactorioLogParser(ConfigModelI& conf) :
|
||||
_log_file_path(conf.get_string("FactorioLogParser", "log_file_path").value_or("factorio-current.log")),
|
||||
_fw(_log_file_path, [this](const auto& path, const auto event){ this->onFileEvent(path, event);})
|
||||
{
|
||||
resetLogFile();
|
||||
}
|
||||
|
||||
FactorioLogParser::~FactorioLogParser(void) {
|
||||
}
|
||||
|
||||
float FactorioLogParser::tick(float delta) {
|
||||
{ // making sure, incase mod events dont work
|
||||
_manual_timer += delta;
|
||||
if (_manual_timer >= 10.f) {
|
||||
_manual_timer = 0.f;
|
||||
if (_log_file.is_open()) {
|
||||
readLines();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard lg{_event_queue_mutex};
|
||||
while (!_event_queue.empty()) {
|
||||
dispatchRaw(_event_queue.front().event, _event_queue.front().params);
|
||||
_event_queue.pop();
|
||||
}
|
||||
|
||||
return 10.f;
|
||||
}
|
||||
|
||||
void FactorioLogParser::onFileEvent(const std::string& path, const filewatch::Event change_type) {
|
||||
// compare path?
|
||||
|
||||
if (change_type == filewatch::Event::added) {
|
||||
// on create, close open log file and reopen and skip to end
|
||||
resetLogFile();
|
||||
} else if (change_type == filewatch::Event::modified) {
|
||||
// on mod, read lines and parse
|
||||
if (!_log_file.is_open()) {
|
||||
std::cerr << "FLP: modified file not open!\n";
|
||||
//resetLogFile();
|
||||
} else {
|
||||
readLines();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FactorioLogParser::resetLogFile(void) {
|
||||
std::cerr << "FLP: resetting log file\n";
|
||||
if (_log_file.is_open()) {
|
||||
_log_file.close();
|
||||
_log_file.clear();
|
||||
}
|
||||
_log_file.open(_log_file_path, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!_log_file.is_open()) {
|
||||
std::cerr << "FLP error: failed opening file\n";
|
||||
}
|
||||
}
|
||||
|
||||
void FactorioLogParser::readLines(void) {
|
||||
std::string line;
|
||||
while (std::getline(_log_file, line).good()) {
|
||||
if (line.empty()) {
|
||||
std::cerr << "FLP error: getline empty??\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto parse_res = log_parse_line(line);
|
||||
if (parse_res.has_value()) {
|
||||
queueRaw(parse_res.value().event, parse_res.value().params);
|
||||
}
|
||||
}
|
||||
_log_file.clear(); // reset eof and fail bits
|
||||
}
|
||||
|
||||
void FactorioLogParser::queueRaw(std::string_view event, std::string_view params) {
|
||||
std::lock_guard lg{_event_queue_mutex};
|
||||
_event_queue.push(EventEntry{static_cast<std::string>(event), static_cast<std::string>(params)});
|
||||
//std::cerr << "enqued '" << event << "': '" << params << "'\n";
|
||||
}
|
||||
|
||||
void FactorioLogParser::dispatchRaw(std::string_view event, std::string_view params) {
|
||||
if (event == "JOIN") {
|
||||
throwJoin(params);
|
||||
} else if (event == "LEAVE") {
|
||||
throwLeave(params);
|
||||
} else if (event == "CHAT") {
|
||||
throwChat(params);
|
||||
} else if (event == "DIED") {
|
||||
throwDied(params);
|
||||
} else if (event == "EVOLUTION") {
|
||||
throwEvolution(params);
|
||||
} else if (event == "RESEARCH STARTED") {
|
||||
throwResearchStarted(params);
|
||||
} else if (event == "RESEARCH FINISHED") {
|
||||
throwResearchFinished(params);
|
||||
} else if (event == "RESEARCH CANCELLED") {
|
||||
throwResearchCancelled(params);
|
||||
} else {
|
||||
std::cerr << "FLP error: unknown event parsed: '" << event << "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwJoin(std::string_view params) {
|
||||
if (params == "<server>") {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
FactorioLogParser_Event::join,
|
||||
FactorioLog::Events::Join{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwLeave(std::string_view params) {
|
||||
const auto user = params.substr(0, params.find_first_of(' '));
|
||||
if (user.empty() || user.size() == params.size()) {
|
||||
std::cerr << "FLP error: invalid LEAVE params: '" << params << "'\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == "<server>") {
|
||||
return;
|
||||
}
|
||||
|
||||
auto reason = params;
|
||||
reason.remove_prefix(user.size()+1);
|
||||
|
||||
dispatch(
|
||||
FactorioLogParser_Event::leave,
|
||||
FactorioLog::Events::Leave{
|
||||
user,
|
||||
reason
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwChat(std::string_view params) {
|
||||
const auto user = params.substr(0, params.find_first_of(':'));
|
||||
|
||||
if (user.empty() || user.size() == params.size() || user.size() + 2 > params.size()) {
|
||||
std::cerr << "FLP error: invalid CHAT params: '" << params << "'\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == "<server>") {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = params;
|
||||
message.remove_prefix(user.size()+2); // ": "
|
||||
if (message.empty()) {
|
||||
std::cerr << "FLP error: empty message? '" << params << "'\n";
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
FactorioLogParser_Event::chat,
|
||||
FactorioLog::Events::Chat{
|
||||
user,
|
||||
message
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwDied(std::string_view params) {
|
||||
const auto user = params.substr(0, params.find_first_of(' '));
|
||||
if (user.empty() || user.size() == params.size()) {
|
||||
std::cerr << "FLP error: invalid DIED params: '" << params << "'\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == "<server>") {
|
||||
return;
|
||||
}
|
||||
|
||||
auto reason = params;
|
||||
reason.remove_prefix(user.size()+1);
|
||||
|
||||
dispatch(
|
||||
FactorioLogParser_Event::died,
|
||||
FactorioLog::Events::Died{
|
||||
user,
|
||||
reason
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwEvolution(std::string_view params) {
|
||||
if (params.empty()) {
|
||||
return; // ???
|
||||
}
|
||||
|
||||
dispatch(
|
||||
FactorioLogParser_Event::evolution,
|
||||
FactorioLog::Events::Evolution{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwResearchStarted(std::string_view params) {
|
||||
dispatch(
|
||||
FactorioLogParser_Event::research_started,
|
||||
FactorioLog::Events::ResearchStarted{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwResearchFinished(std::string_view params) {
|
||||
dispatch(
|
||||
FactorioLogParser_Event::research_finished,
|
||||
FactorioLog::Events::ResearchFinished{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FactorioLogParser::throwResearchCancelled(std::string_view params) {
|
||||
dispatch(
|
||||
FactorioLogParser_Event::research_cancelled,
|
||||
FactorioLog::Events::ResearchCancelled{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
124
src/factorio_log_parser.hpp
Normal file
124
src/factorio_log_parser.hpp
Normal file
@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/util/event_provider.hpp>
|
||||
|
||||
#include <FileWatch.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <fstream>
|
||||
|
||||
// fwd
|
||||
struct ConfigModelI;
|
||||
|
||||
namespace FactorioLog::Events {
|
||||
|
||||
struct Join {
|
||||
std::string_view player_name;
|
||||
};
|
||||
|
||||
struct Leave {
|
||||
std::string_view player_name;
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
struct Chat {
|
||||
std::string_view player_name;
|
||||
std::string_view message;
|
||||
};
|
||||
|
||||
struct Died {
|
||||
std::string_view player_name;
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
struct Evolution {
|
||||
// ?
|
||||
std::string_view evo;
|
||||
};
|
||||
|
||||
struct ResearchStarted {
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
struct ResearchFinished {
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
struct ResearchCancelled {
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
} // FactorioLog::Events
|
||||
|
||||
enum class FactorioLogParser_Event : uint32_t {
|
||||
join,
|
||||
leave,
|
||||
chat,
|
||||
died,
|
||||
evolution,
|
||||
research_started,
|
||||
research_finished,
|
||||
research_cancelled,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
struct FactorioLogParserEventI {
|
||||
using enumType = FactorioLogParser_Event;
|
||||
|
||||
virtual ~FactorioLogParserEventI(void) {}
|
||||
|
||||
virtual bool onEvent(const FactorioLog::Events::Join&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::Leave&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::Chat&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::Died&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::Evolution&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::ResearchStarted&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::ResearchFinished&) { return false; }
|
||||
virtual bool onEvent(const FactorioLog::Events::ResearchCancelled&) { return false; }
|
||||
};
|
||||
using FactorioLogParserEventProviderI = EventProviderI<FactorioLogParserEventI>;
|
||||
|
||||
class FactorioLogParser : public FactorioLogParserEventProviderI {
|
||||
std::string _log_file_path;
|
||||
filewatch::FileWatch<std::string> _fw;
|
||||
std::ifstream _log_file;
|
||||
|
||||
struct EventEntry {
|
||||
std::string event;
|
||||
std::string params;
|
||||
};
|
||||
std::queue<EventEntry> _event_queue;
|
||||
std::mutex _event_queue_mutex;
|
||||
|
||||
float _manual_timer {1.f};
|
||||
|
||||
public:
|
||||
FactorioLogParser(ConfigModelI& conf);
|
||||
virtual ~FactorioLogParser(void);
|
||||
|
||||
float tick(float delta);
|
||||
|
||||
protected:
|
||||
void onFileEvent(const std::string& path, const filewatch::Event change_type);
|
||||
void resetLogFile(void);
|
||||
// assumes file is open!
|
||||
void readLines(void);
|
||||
|
||||
protected:
|
||||
void queueRaw(std::string_view event, std::string_view params);
|
||||
void dispatchRaw(std::string_view event, std::string_view params);
|
||||
|
||||
void throwJoin(std::string_view params);
|
||||
void throwLeave(std::string_view params);
|
||||
void throwChat(std::string_view params);
|
||||
void throwDied(std::string_view params);
|
||||
void throwEvolution(std::string_view params);
|
||||
void throwResearchStarted(std::string_view params);
|
||||
void throwResearchFinished(std::string_view params);
|
||||
void throwResearchCancelled(std::string_view params);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ std::optional<LPLRes> log_parse_line(std::string_view line) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// TODO: make mod name configurable
|
||||
static const std::regex mod_match{".*Factorio-Event-Logger+.*\\[([A-Z ]+)\\] (.+)$"};
|
||||
|
||||
std::cmatch matches;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
struct LPLRes {
|
||||
std::string_view event;
|
||||
std::string_view info;
|
||||
std::string_view params;
|
||||
};
|
||||
|
||||
std::optional<LPLRes> log_parse_line(std::string_view line);
|
||||
|
@ -8,7 +8,7 @@ int main(void) {
|
||||
const auto parse_res = log_parse_line(" 442.539 Script @__Factorio-Event-Logger__/logger.lua:65: [RESEARCH CANCELLED] worker-robots-speed");
|
||||
assert(parse_res.has_value());
|
||||
assert(parse_res.value().event == "RESEARCH CANCELLED");
|
||||
assert(parse_res.value().info == "worker-robots-speed");
|
||||
assert(parse_res.value().params == "worker-robots-speed");
|
||||
}
|
||||
|
||||
{
|
||||
|
Reference in New Issue
Block a user