Compare commits

...

17 Commits

Author SHA1 Message Date
161f605b20 port to contact4
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2025-03-10 22:57:15 +01:00
6aa90a1852 make forwarding configurable
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2025-01-12 13:37:35 +01:00
f69923cbbb ignore pings, the polute the chat and are unreadable without context
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2025-01-09 19:43:20 +01:00
b62ab60366 Create LICENSE 2025-01-06 18:11:52 +01:00
117e40dc9e use sr
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2024-10-25 13:57:14 +02:00
050c7f3be1 update for rmmi
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2024-10-06 12:47:04 +02:00
4c6beb592c fallback for no file mod events
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Failing after 21s
ContinuousIntegration / linux (push) Successful in 21s
ContinuousDelivery / windows (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
2024-06-12 22:24:47 +02:00
a91b4d41dd hardcode some events (working)
Some checks failed
ContinuousDelivery / windows (push) Waiting to run
ContinuousDelivery / release (push) Blocked by required conditions
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run
ContinuousDelivery / linux-ubuntu (push) Failing after 22s
ContinuousIntegration / linux (push) Successful in 22s
2024-06-12 21:30:51 +02:00
d18ec4e1ca load linked contacts from config (not sending yet)
Some checks failed
ContinuousDelivery / windows (push) Waiting to run
ContinuousDelivery / release (push) Blocked by required conditions
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run
ContinuousDelivery / linux-ubuntu (push) Failing after 21s
ContinuousIntegration / linux (push) Successful in 22s
2024-06-12 21:11:12 +02:00
7c3b867c7c add cd 2024-06-12 20:13:02 +02:00
e1e2563ac2 load path from config, follow log file, working reopening
Some checks failed
ContinuousIntegration / linux (push) Successful in 23s
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
2024-06-11 12:02:53 +02:00
6f6894419a event parsing and dispatching
Some checks are pending
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run
ContinuousIntegration / linux (push) Successful in 22s
2024-06-11 10:37:20 +02:00
70317ab4db more event boilerplate
Some checks are pending
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run
ContinuousIntegration / linux (push) Successful in 22s
2024-06-11 10:09:55 +02:00
4295c6cc53 separate out the log parsing, will throw events 2024-06-11 09:45:52 +02:00
06bd55c165 fix link
Some checks are pending
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run
ContinuousIntegration / linux (push) Successful in 20s
2024-06-10 22:39:56 +02:00
f275cb02d4 fix macos 2024-06-10 22:37:46 +02:00
95552a2bf3 switch to https://github.com/ThomasMonkman/filewatch/pull/45 2024-06-10 22:35:05 +02:00
12 changed files with 752 additions and 26 deletions

159
.github/workflows/cd.yml vendored Normal file
View 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
View 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.

View File

@ -22,8 +22,10 @@ endif()
if (NOT TARGET filewatch) if (NOT TARGET filewatch)
FetchContent_Declare(filewatch FetchContent_Declare(filewatch
GIT_REPOSITORY https://github.com/ThomasMonkman/filewatch.git #GIT_REPOSITORY https://github.com/ThomasMonkman/filewatch.git
GIT_TAG master GIT_REPOSITORY https://github.com/justinboswell/filewatch
#GIT_TAG master
GIT_TAG 2cef7983449579555799065f31d022b86b739d62
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
) )
@ -37,5 +39,8 @@ if (NOT TARGET filewatch)
) )
target_include_directories(filewatch INTERFACE ${filewatch_SOURCE_DIR}) target_include_directories(filewatch INTERFACE ${filewatch_SOURCE_DIR})
target_compile_features(filewatch INTERFACE cxx_std_11) target_compile_features(filewatch INTERFACE cxx_std_11)
if(APPLE)
target_link_libraries(filewatch INTERFACE "-framework CoreServices")
endif()
endif() endif()
endif() endif()

View File

@ -3,10 +3,14 @@
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <entt/fwd.hpp> #include <entt/fwd.hpp>
#include <solanaceae/util/config_model.hpp>
#include "factorio_log_parser.hpp"
#include "factorio.hpp" #include "factorio.hpp"
#include <iostream> #include <iostream>
static std::unique_ptr<FactorioLogParser> g_flp = nullptr;
static std::unique_ptr<Factorio> g_f = nullptr; static std::unique_ptr<Factorio> g_f = nullptr;
constexpr const char* plugin_name = "Factorio"; constexpr const char* plugin_name = "Factorio";
@ -29,14 +33,17 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api)
} }
try { try {
auto* cr = PLUG_RESOLVE_INSTANCE_VERSIONED(Contact3Registry, "1"); auto* conf = PLUG_RESOLVE_INSTANCE(ConfigModelI);
auto* rmm = PLUG_RESOLVE_INSTANCE(RegistryMessageModel); auto* cs = PLUG_RESOLVE_INSTANCE(ContactStore4I);
auto* rmm = PLUG_RESOLVE_INSTANCE(RegistryMessageModelI);
// static store, could be anywhere tho // static store, could be anywhere tho
// construct with fetched dependencies // 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 // register types
PLUG_PROVIDE_INSTANCE(FactorioLogParser, plugin_name, g_flp.get());
PLUG_PROVIDE_INSTANCE(Factorio, plugin_name, g_f.get()); PLUG_PROVIDE_INSTANCE(Factorio, plugin_name, g_f.get());
} catch (const ResolveException& e) { } catch (const ResolveException& e) {
std::cerr << "PLUGIN " << plugin_name << " " << e.what << "\n"; 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"; std::cout << "PLUGIN " << plugin_name << " STOP()\n";
g_f.reset(); g_f.reset();
g_flp.reset();
} }
SOLANA_PLUGIN_EXPORT float solana_plugin_tick(float) { SOLANA_PLUGIN_EXPORT float solana_plugin_tick(float delta) {
//return g_rpbot->tick(delta); return g_flp->tick(delta);
return 1000.f;
} }
} // extern C } // extern C

View File

@ -5,6 +5,8 @@ project(solanaceae)
add_library(solanaceae_factorio add_library(solanaceae_factorio
./log_parse.hpp ./log_parse.hpp
./log_parse.cpp ./log_parse.cpp
./factorio_log_parser.hpp
./factorio_log_parser.cpp
./factorio.hpp ./factorio.hpp
./factorio.cpp ./factorio.cpp
) )

View File

@ -1,18 +1,61 @@
#include "./factorio.hpp" #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/message3/components.hpp>
#include <solanaceae/contact/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) : _rmm.sendText(h, message);
_cr(cr), }
}
Factorio::Factorio(ConfigModelI& conf, ContactStore4I& cs, RegistryMessageModelI& rmm, FactorioLogParser& flp) :
_conf(conf),
_cs(cs),
_rmm(rmm), _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) { Factorio::~Factorio(void) {
@ -22,7 +65,118 @@ bool Factorio::onEvent(const Message::Events::MessageConstruct& e) {
return false; return false;
} }
void onFileEvent(const std::string& path, const filewatch::Event change_type) { bool Factorio::onEvent(const FactorioLog::Events::Join& e) {
std::cout << "file even " << filewatch::event_to_string(change_type) << " on '" << path << "'\n"; 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;
} }

View File

@ -1,25 +1,42 @@
#pragma once #pragma once
#include <solanaceae/contact/fwd.hpp>
#include <solanaceae/message3/registry_message_model.hpp> #include <solanaceae/message3/registry_message_model.hpp>
#include <FileWatch.hpp> #include "./factorio_log_parser.hpp"
#include <string> #include <vector>
class Factorio : public RegistryMessageModelEventI { // fwd
Contact3Registry& _cr; struct ConfigModelI;
RegistryMessageModel& _rmm;
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: public:
Factorio(Contact3Registry& cr, RegistryMessageModel& rmm); Factorio(ConfigModelI& conf, ContactStore4I& cs, RegistryMessageModelI& rmm, FactorioLogParser& flp);
virtual ~Factorio(void); virtual ~Factorio(void);
protected: // rmm protected: // rmm
bool onEvent(const Message::Events::MessageConstruct& e) override; bool onEvent(const Message::Events::MessageConstruct& e) override;
protected: protected: // flp
void onFileEvent(const std::string& path, const filewatch::Event change_type); 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
View 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
View 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);
};

View File

@ -7,6 +7,7 @@ std::optional<LPLRes> log_parse_line(std::string_view line) {
return std::nullopt; return std::nullopt;
} }
// TODO: make mod name configurable
static const std::regex mod_match{".*Factorio-Event-Logger+.*\\[([A-Z ]+)\\] (.+)$"}; static const std::regex mod_match{".*Factorio-Event-Logger+.*\\[([A-Z ]+)\\] (.+)$"};
std::cmatch matches; std::cmatch matches;

View File

@ -5,7 +5,7 @@
struct LPLRes { struct LPLRes {
std::string_view event; std::string_view event;
std::string_view info; std::string_view params;
}; };
std::optional<LPLRes> log_parse_line(std::string_view line); std::optional<LPLRes> log_parse_line(std::string_view line);

View File

@ -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"); 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.has_value());
assert(parse_res.value().event == "RESEARCH CANCELLED"); assert(parse_res.value().event == "RESEARCH CANCELLED");
assert(parse_res.value().info == "worker-robots-speed"); assert(parse_res.value().params == "worker-robots-speed");
} }
{ {