feat(eventbus): Add ZeroMQ event bus infrastructure

- Add libzmq dependency via pkg-config in dep/zeromq/CMakeLists.txt
- Add cppzmq header-only bindings
- Create AraxiaEventBus singleton with PUB/SUB sockets
- Add background worker thread for ZMQ I/O
- Add message envelope with JSON format (v, topic, ts, source, context, payload)
- Add context-aware topic routing (world/dungeon/raid/bg/arena)
- Add AraxiaEventBusConfig for loading settings from worldserver.conf
- Integrate EventBus init/update/shutdown into World lifecycle
- Add configuration options to worldserver.conf.dist

Part of OpenSpec: add-zeromq-event-bus
This commit is contained in:
2025-12-15 09:58:46 -05:00
parent 6bb035cfc3
commit 95c30fbdf8
12 changed files with 4369 additions and 1 deletions

View File

@@ -33,6 +33,7 @@ if(SERVERS)
add_subdirectory(rapidjson)
add_subdirectory(efsw)
add_subdirectory(protobuf)
add_subdirectory(zeromq)
endif()
if(TOOLS)

40
dep/zeromq/CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
# This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# ZeroMQ Event Bus Integration
# This provides pub/sub messaging for server events, encounter monitoring,
# and client addon communication via AMS bridge.
# See: openspec/changes/add-zeromq-event-bus/
# Find system libzmq
find_package(PkgConfig REQUIRED)
pkg_check_modules(ZMQ REQUIRED libzmq)
# Create interface library for ZeroMQ
add_library(zeromq INTERFACE)
target_include_directories(zeromq
INTERFACE
${ZMQ_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/cppzmq)
target_link_libraries(zeromq
INTERFACE
${ZMQ_LIBRARIES})
target_link_directories(zeromq
INTERFACE
${ZMQ_LIBRARY_DIRS})
target_compile_options(zeromq
INTERFACE
${ZMQ_CFLAGS_OTHER})
message(STATUS "Found ZeroMQ: ${ZMQ_VERSION}")

2762
dep/zeromq/cppzmq/zmq.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,753 @@
/*
Copyright (c) 2016-2017 ZeroMQ community
Copyright (c) 2016 VOCA AS / Harald Nøkland
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.
*/
#ifndef __ZMQ_ADDON_HPP_INCLUDED__
#define __ZMQ_ADDON_HPP_INCLUDED__
#include "zmq.hpp"
#include <deque>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#ifdef ZMQ_CPP11
#include <limits>
#include <functional>
#include <unordered_map>
#endif
namespace zmq
{
#ifdef ZMQ_CPP11
namespace detail
{
template<bool CheckN, class OutputIt>
recv_result_t
recv_multipart_n(socket_ref s, OutputIt out, size_t n, recv_flags flags)
{
size_t msg_count = 0;
message_t msg;
while (true) {
if ZMQ_CONSTEXPR_IF (CheckN) {
if (msg_count >= n)
throw std::runtime_error(
"Too many message parts in recv_multipart_n");
}
if (!s.recv(msg, flags)) {
// zmq ensures atomic delivery of messages
assert(msg_count == 0);
return {};
}
++msg_count;
const bool more = msg.more();
*out++ = std::move(msg);
if (!more)
break;
}
return msg_count;
}
inline bool is_little_endian()
{
const uint16_t i = 0x01;
return *reinterpret_cast<const uint8_t *>(&i) == 0x01;
}
inline void write_network_order(unsigned char *buf, const uint32_t value)
{
if (is_little_endian()) {
ZMQ_CONSTEXPR_VAR uint32_t mask = (std::numeric_limits<std::uint8_t>::max)();
*buf++ = static_cast<unsigned char>((value >> 24) & mask);
*buf++ = static_cast<unsigned char>((value >> 16) & mask);
*buf++ = static_cast<unsigned char>((value >> 8) & mask);
*buf++ = static_cast<unsigned char>(value & mask);
} else {
std::memcpy(buf, &value, sizeof(value));
}
}
inline uint32_t read_u32_network_order(const unsigned char *buf)
{
if (is_little_endian()) {
return (static_cast<uint32_t>(buf[0]) << 24)
+ (static_cast<uint32_t>(buf[1]) << 16)
+ (static_cast<uint32_t>(buf[2]) << 8)
+ static_cast<uint32_t>(buf[3]);
} else {
uint32_t value;
std::memcpy(&value, buf, sizeof(value));
return value;
}
}
} // namespace detail
/* Receive a multipart message.
Writes the zmq::message_t objects to OutputIterator out.
The out iterator must handle an unspecified number of writes,
e.g. by using std::back_inserter.
Returns: the number of messages received or nullopt (on EAGAIN).
Throws: if recv throws. Any exceptions thrown
by the out iterator will be propagated and the message
may have been only partially received with pending
message parts. It is adviced to close this socket in that event.
*/
template<class OutputIt>
ZMQ_NODISCARD recv_result_t recv_multipart(socket_ref s,
OutputIt out,
recv_flags flags = recv_flags::none)
{
return detail::recv_multipart_n<false>(s, std::move(out), 0, flags);
}
/* Receive a multipart message.
Writes at most n zmq::message_t objects to OutputIterator out.
If the number of message parts of the incoming message exceeds n
then an exception will be thrown.
Returns: the number of messages received or nullopt (on EAGAIN).
Throws: if recv throws. Throws std::runtime_error if the number
of message parts exceeds n (exactly n messages will have been written
to out). Any exceptions thrown
by the out iterator will be propagated and the message
may have been only partially received with pending
message parts. It is adviced to close this socket in that event.
*/
template<class OutputIt>
ZMQ_NODISCARD recv_result_t recv_multipart_n(socket_ref s,
OutputIt out,
size_t n,
recv_flags flags = recv_flags::none)
{
return detail::recv_multipart_n<true>(s, std::move(out), n, flags);
}
/* Send a multipart message.
The range must be a ForwardRange of zmq::message_t,
zmq::const_buffer or zmq::mutable_buffer.
The flags may be zmq::send_flags::sndmore if there are
more message parts to be sent after the call to this function.
Returns: the number of messages sent (exactly msgs.size()) or nullopt (on EAGAIN).
Throws: if send throws. Any exceptions thrown
by the msgs range will be propagated and the message
may have been only partially sent. It is adviced to close this socket in that event.
*/
template<class Range
#ifndef ZMQ_CPP11_PARTIAL
,
typename = typename std::enable_if<
detail::is_range<Range>::value
&& (std::is_same<detail::range_value_t<Range>, message_t>::value
|| detail::is_buffer<detail::range_value_t<Range>>::value)>::type
#endif
>
send_result_t
send_multipart(socket_ref s, Range &&msgs, send_flags flags = send_flags::none)
{
using std::begin;
using std::end;
auto it = begin(msgs);
const auto end_it = end(msgs);
size_t msg_count = 0;
while (it != end_it) {
const auto next = std::next(it);
const auto msg_flags =
flags | (next == end_it ? send_flags::none : send_flags::sndmore);
if (!s.send(*it, msg_flags)) {
// zmq ensures atomic delivery of messages
assert(it == begin(msgs));
return {};
}
++msg_count;
it = next;
}
return msg_count;
}
/* Encode a multipart message.
The range must be a ForwardRange of zmq::message_t. A
zmq::multipart_t or STL container may be passed for encoding.
Returns: a zmq::message_t holding the encoded multipart data.
Throws: std::range_error is thrown if the size of any single part
can not fit in an unsigned 32 bit integer.
The encoding is compatible with that used by the CZMQ function
zmsg_encode(), see https://rfc.zeromq.org/spec/50/.
Each part consists of a size followed by the data.
These are placed contiguously into the output message. A part of
size less than 255 bytes will have a single byte size value.
Larger parts will have a five byte size value with the first byte
set to 0xFF and the remaining four bytes holding the size of the
part's data.
*/
template<class Range
#ifndef ZMQ_CPP11_PARTIAL
,
typename = typename std::enable_if<
detail::is_range<Range>::value
&& (std::is_same<detail::range_value_t<Range>, message_t>::value
|| detail::is_buffer<detail::range_value_t<Range>>::value)>::type
#endif
>
message_t encode(const Range &parts)
{
size_t mmsg_size = 0;
// First pass check sizes
for (const auto &part : parts) {
const size_t part_size = part.size();
if (part_size > (std::numeric_limits<std::uint32_t>::max)()) {
// Size value must fit into uint32_t.
throw std::range_error("Invalid size, message part too large");
}
const size_t count_size =
part_size < (std::numeric_limits<std::uint8_t>::max)() ? 1 : 5;
mmsg_size += part_size + count_size;
}
message_t encoded(mmsg_size);
unsigned char *buf = encoded.data<unsigned char>();
for (const auto &part : parts) {
const uint32_t part_size = static_cast<uint32_t>(part.size());
const unsigned char *part_data =
static_cast<const unsigned char *>(part.data());
if (part_size < (std::numeric_limits<std::uint8_t>::max)()) {
// small part
*buf++ = (unsigned char) part_size;
} else {
// big part
*buf++ = (std::numeric_limits<uint8_t>::max)();
detail::write_network_order(buf, part_size);
buf += sizeof(part_size);
}
std::memcpy(buf, part_data, part_size);
buf += part_size;
}
assert(static_cast<size_t>(buf - encoded.data<unsigned char>()) == mmsg_size);
return encoded;
}
/* Decode an encoded message to multiple parts.
The given output iterator must be a ForwardIterator to a container
holding zmq::message_t such as a zmq::multipart_t or various STL
containers.
Returns the ForwardIterator advanced once past the last decoded
part.
Throws: a std::out_of_range is thrown if the encoded part sizes
lead to exceeding the message data bounds.
The decoding assumes the message is encoded in the manner
performed by zmq::encode(), see https://rfc.zeromq.org/spec/50/.
*/
template<class OutputIt> OutputIt decode(const message_t &encoded, OutputIt out)
{
const unsigned char *source = encoded.data<unsigned char>();
const unsigned char *const limit = source + encoded.size();
while (source < limit) {
size_t part_size = *source++;
if (part_size == (std::numeric_limits<std::uint8_t>::max)()) {
if (static_cast<size_t>(limit - source) < sizeof(uint32_t)) {
throw std::out_of_range(
"Malformed encoding, overflow in reading size");
}
part_size = detail::read_u32_network_order(source);
// the part size is allowed to be less than 0xFF
source += sizeof(uint32_t);
}
if (static_cast<size_t>(limit - source) < part_size) {
throw std::out_of_range("Malformed encoding, overflow in reading part");
}
*out = message_t(source, part_size);
++out;
source += part_size;
}
assert(source == limit);
return out;
}
#endif
#ifdef ZMQ_HAS_RVALUE_REFS
/*
This class handles multipart messaging. It is the C++ equivalent of zmsg.h,
which is part of CZMQ (the high-level C binding). Furthermore, it is a major
improvement compared to zmsg.hpp, which is part of the examples in the ØMQ
Guide. Unnecessary copying is avoided by using move semantics to efficiently
add/remove parts.
*/
class multipart_t
{
private:
std::deque<message_t> m_parts;
public:
typedef std::deque<message_t>::value_type value_type;
typedef std::deque<message_t>::iterator iterator;
typedef std::deque<message_t>::const_iterator const_iterator;
typedef std::deque<message_t>::reverse_iterator reverse_iterator;
typedef std::deque<message_t>::const_reverse_iterator const_reverse_iterator;
// Default constructor
multipart_t() {}
// Construct from socket receive
multipart_t(socket_ref socket) { recv(socket); }
// Construct from memory block
multipart_t(const void *src, size_t size) { addmem(src, size); }
// Construct from string
multipart_t(const std::string &string) { addstr(string); }
// Construct from message part
multipart_t(message_t &&message) { add(std::move(message)); }
// Move constructor
multipart_t(multipart_t &&other) ZMQ_NOTHROW { m_parts = std::move(other.m_parts); }
// Move assignment operator
multipart_t &operator=(multipart_t &&other) ZMQ_NOTHROW
{
m_parts = std::move(other.m_parts);
return *this;
}
// Destructor
virtual ~multipart_t() { clear(); }
message_t &operator[](size_t n) { return m_parts[n]; }
const message_t &operator[](size_t n) const { return m_parts[n]; }
message_t &at(size_t n) { return m_parts.at(n); }
const message_t &at(size_t n) const { return m_parts.at(n); }
iterator begin() { return m_parts.begin(); }
const_iterator begin() const { return m_parts.begin(); }
const_iterator cbegin() const { return m_parts.cbegin(); }
reverse_iterator rbegin() { return m_parts.rbegin(); }
const_reverse_iterator rbegin() const { return m_parts.rbegin(); }
iterator end() { return m_parts.end(); }
const_iterator end() const { return m_parts.end(); }
const_iterator cend() const { return m_parts.cend(); }
reverse_iterator rend() { return m_parts.rend(); }
const_reverse_iterator rend() const { return m_parts.rend(); }
// Delete all parts
void clear() { m_parts.clear(); }
// Get number of parts
size_t size() const { return m_parts.size(); }
// Check if number of parts is zero
bool empty() const { return m_parts.empty(); }
// Receive multipart message from socket
bool recv(socket_ref socket, int flags = 0)
{
clear();
bool more = true;
while (more) {
message_t message;
#ifdef ZMQ_CPP11
if (!socket.recv(message, static_cast<recv_flags>(flags)))
return false;
#else
if (!socket.recv(&message, flags))
return false;
#endif
more = message.more();
add(std::move(message));
}
return true;
}
// Send multipart message to socket
bool send(socket_ref socket, int flags = 0)
{
flags &= ~(ZMQ_SNDMORE);
bool more = size() > 0;
while (more) {
message_t message = pop();
more = size() > 0;
#ifdef ZMQ_CPP11
if (!socket.send(message, static_cast<send_flags>(
(more ? ZMQ_SNDMORE : 0) | flags)))
return false;
#else
if (!socket.send(message, (more ? ZMQ_SNDMORE : 0) | flags))
return false;
#endif
}
clear();
return true;
}
// Concatenate other multipart to front
void prepend(multipart_t &&other)
{
while (!other.empty())
push(other.remove());
}
// Concatenate other multipart to back
void append(multipart_t &&other)
{
while (!other.empty())
add(other.pop());
}
// Push memory block to front
void pushmem(const void *src, size_t size)
{
m_parts.push_front(message_t(src, size));
}
// Push memory block to back
void addmem(const void *src, size_t size)
{
m_parts.push_back(message_t(src, size));
}
// Push string to front
void pushstr(const std::string &string)
{
m_parts.push_front(message_t(string.data(), string.size()));
}
// Push string to back
void addstr(const std::string &string)
{
m_parts.push_back(message_t(string.data(), string.size()));
}
// Push type (fixed-size) to front
template<typename T> void pushtyp(const T &type)
{
static_assert(!std::is_same<T, std::string>::value,
"Use pushstr() instead of pushtyp<std::string>()");
m_parts.push_front(message_t(&type, sizeof(type)));
}
// Push type (fixed-size) to back
template<typename T> void addtyp(const T &type)
{
static_assert(!std::is_same<T, std::string>::value,
"Use addstr() instead of addtyp<std::string>()");
m_parts.push_back(message_t(&type, sizeof(type)));
}
// Push message part to front
void push(message_t &&message) { m_parts.push_front(std::move(message)); }
// Push message part to back
void add(message_t &&message) { m_parts.push_back(std::move(message)); }
// Alias to allow std::back_inserter()
void push_back(message_t &&message) { m_parts.push_back(std::move(message)); }
// Pop string from front
std::string popstr()
{
std::string string(m_parts.front().data<char>(), m_parts.front().size());
m_parts.pop_front();
return string;
}
// Pop type (fixed-size) from front
template<typename T> T poptyp()
{
static_assert(!std::is_same<T, std::string>::value,
"Use popstr() instead of poptyp<std::string>()");
if (sizeof(T) != m_parts.front().size())
throw std::runtime_error(
"Invalid type, size does not match the message size");
T type = *m_parts.front().data<T>();
m_parts.pop_front();
return type;
}
// Pop message part from front
message_t pop()
{
message_t message = std::move(m_parts.front());
m_parts.pop_front();
return message;
}
// Pop message part from back
message_t remove()
{
message_t message = std::move(m_parts.back());
m_parts.pop_back();
return message;
}
// get message part from front
const message_t &front() { return m_parts.front(); }
// get message part from back
const message_t &back() { return m_parts.back(); }
// Get pointer to a specific message part
const message_t *peek(size_t index) const { return &m_parts[index]; }
// Get a string copy of a specific message part
std::string peekstr(size_t index) const
{
std::string string(m_parts[index].data<char>(), m_parts[index].size());
return string;
}
// Peek type (fixed-size) from front
template<typename T> T peektyp(size_t index) const
{
static_assert(!std::is_same<T, std::string>::value,
"Use peekstr() instead of peektyp<std::string>()");
if (sizeof(T) != m_parts[index].size())
throw std::runtime_error(
"Invalid type, size does not match the message size");
T type = *m_parts[index].data<T>();
return type;
}
// Create multipart from type (fixed-size)
template<typename T> static multipart_t create(const T &type)
{
multipart_t multipart;
multipart.addtyp(type);
return multipart;
}
// Copy multipart
multipart_t clone() const
{
multipart_t multipart;
for (size_t i = 0; i < size(); i++)
multipart.addmem(m_parts[i].data(), m_parts[i].size());
return multipart;
}
// Dump content to string
std::string str() const
{
std::stringstream ss;
for (size_t i = 0; i < m_parts.size(); i++) {
const unsigned char *data = m_parts[i].data<unsigned char>();
size_t size = m_parts[i].size();
// Dump the message as text or binary
bool isText = true;
for (size_t j = 0; j < size; j++) {
if (data[j] < 32 || data[j] > 127) {
isText = false;
break;
}
}
ss << "\n[" << std::dec << std::setw(3) << std::setfill('0') << size
<< "] ";
if (size >= 1000) {
ss << "... (too big to print)";
continue;
}
for (size_t j = 0; j < size; j++) {
if (isText)
ss << static_cast<char>(data[j]);
else
ss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<short>(data[j]);
}
}
return ss.str();
}
// Check if equal to other multipart
bool equal(const multipart_t *other) const ZMQ_NOTHROW
{
return *this == *other;
}
bool operator==(const multipart_t &other) const ZMQ_NOTHROW
{
if (size() != other.size())
return false;
for (size_t i = 0; i < size(); i++)
if (at(i) != other.at(i))
return false;
return true;
}
bool operator!=(const multipart_t &other) const ZMQ_NOTHROW
{
return !(*this == other);
}
#ifdef ZMQ_CPP11
// Return single part message_t encoded from this multipart_t.
message_t encode() const { return zmq::encode(*this); }
// Decode encoded message into multiple parts and append to self.
void decode_append(const message_t &encoded)
{
zmq::decode(encoded, std::back_inserter(*this));
}
// Return a new multipart_t containing the decoded message_t.
static multipart_t decode(const message_t &encoded)
{
multipart_t tmp;
zmq::decode(encoded, std::back_inserter(tmp));
return tmp;
}
#endif
private:
// Disable implicit copying (moving is more efficient)
multipart_t(const multipart_t &other) ZMQ_DELETED_FUNCTION;
void operator=(const multipart_t &other) ZMQ_DELETED_FUNCTION;
}; // class multipart_t
inline std::ostream &operator<<(std::ostream &os, const multipart_t &msg)
{
return os << msg.str();
}
#endif // ZMQ_HAS_RVALUE_REFS
#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER)
class active_poller_t
{
public:
active_poller_t() = default;
~active_poller_t() = default;
active_poller_t(const active_poller_t &) = delete;
active_poller_t &operator=(const active_poller_t &) = delete;
active_poller_t(active_poller_t &&src) = default;
active_poller_t &operator=(active_poller_t &&src) = default;
using handler_type = std::function<void(event_flags)>;
void add(zmq::socket_ref socket, event_flags events, handler_type handler)
{
if (!handler)
throw std::invalid_argument("null handler in active_poller_t::add");
auto ret = handlers.emplace(
socket, std::make_shared<handler_type>(std::move(handler)));
if (!ret.second)
throw error_t(EINVAL); // already added
try {
base_poller.add(socket, events, ret.first->second.get());
need_rebuild = true;
}
catch (...) {
// rollback
handlers.erase(socket);
throw;
}
}
void remove(zmq::socket_ref socket)
{
base_poller.remove(socket);
handlers.erase(socket);
need_rebuild = true;
}
void modify(zmq::socket_ref socket, event_flags events)
{
base_poller.modify(socket, events);
}
size_t wait(std::chrono::milliseconds timeout)
{
if (need_rebuild) {
poller_events.resize(handlers.size());
poller_handlers.clear();
poller_handlers.reserve(handlers.size());
for (const auto &handler : handlers) {
poller_handlers.push_back(handler.second);
}
need_rebuild = false;
}
const auto count = base_poller.wait_all(poller_events, timeout);
std::for_each(poller_events.begin(),
poller_events.begin() + static_cast<ptrdiff_t>(count),
[](decltype(base_poller)::event_type &event) {
assert(event.user_data != nullptr);
(*event.user_data)(event.events);
});
return count;
}
ZMQ_NODISCARD bool empty() const noexcept { return handlers.empty(); }
size_t size() const noexcept { return handlers.size(); }
private:
bool need_rebuild{false};
poller_t<handler_type> base_poller{};
std::unordered_map<socket_ref, std::shared_ptr<handler_type>> handlers{};
std::vector<decltype(base_poller)::event_type> poller_events{};
std::vector<std::shared_ptr<handler_type>> poller_handlers{};
}; // class active_poller_t
#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER)
} // namespace zmq
#endif // __ZMQ_ADDON_HPP_INCLUDED__

View File

@@ -1,9 +1,15 @@
# Araxia Online - Custom Code
# This folder contains all Araxia-specific customizations
# Build eventbus as separate library (has ZMQ dependency)
add_subdirectory(eventbus)
# Collect sources EXCLUDING eventbus (it's built as separate library)
CollectSourceFiles(
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE_SOURCES)
PRIVATE_SOURCES
SKIP_DIRS
eventbus)
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
@@ -15,6 +21,7 @@ target_include_directories(game
${CMAKE_CURRENT_SOURCE_DIR}/game/Entities
${CMAKE_CURRENT_SOURCE_DIR}/game/Entities/Creature
${CMAKE_CURRENT_SOURCE_DIR}/mcp
${CMAKE_CURRENT_SOURCE_DIR}/eventbus
)
# Add sources to the game target
@@ -23,4 +30,11 @@ target_sources(game
${PRIVATE_SOURCES}
)
# Link eventbus library (brings in ZMQ transitively)
target_link_libraries(game
PRIVATE
araxia-eventbus
zeromq
)
message(STATUS "[Araxia] Custom code sources added")

View File

@@ -0,0 +1,405 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AraxiaEventBus.h"
#include "Log.h"
#include "Timer.h"
#include <zmq.hpp>
#include <sstream>
#include <chrono>
/*
* AraxiaEventBus Implementation
*
* Uses ZeroMQ PUB/SUB pattern for event distribution.
* Publisher socket binds to pubEndpoint (default :5555)
* Subscriber socket connects to subEndpoint (default :5556)
*
* Thread safety:
* - Main thread queues messages via Publish()
* - Worker thread handles actual ZMQ I/O
* - Update() processes inbound messages on main thread
*/
AraxiaEventBus::AraxiaEventBus() = default;
AraxiaEventBus::~AraxiaEventBus()
{
Shutdown();
}
AraxiaEventBus* AraxiaEventBus::Instance()
{
static AraxiaEventBus instance;
return &instance;
}
bool AraxiaEventBus::Initialize(const std::string& pubEndpoint, const std::string& subEndpoint)
{
if (_initialized)
{
TC_LOG_WARN("araxia.eventbus", "AraxiaEventBus already initialized");
return true;
}
_pubEndpoint = pubEndpoint;
_subEndpoint = subEndpoint;
try
{
// Create ZMQ context with 1 I/O thread
_context = std::make_unique<zmq::context_t>(1);
// Create publisher socket
_publisher = std::make_unique<zmq::socket_t>(*_context, zmq::socket_type::pub);
_publisher->bind(_pubEndpoint);
// Create subscriber socket
_subscriber = std::make_unique<zmq::socket_t>(*_context, zmq::socket_type::sub);
_subscriber->connect(_subEndpoint);
// Subscribe to command.* topics by default
_subscriber->set(zmq::sockopt::subscribe, "command.");
// Set socket options for non-blocking receives
_subscriber->set(zmq::sockopt::rcvtimeo, 100); // 100ms timeout
TC_LOG_INFO("araxia.eventbus", "AraxiaEventBus initialized - pub: {}, sub: {}",
_pubEndpoint, _subEndpoint);
// Start worker thread
_running = true;
_workerThread = std::thread(&AraxiaEventBus::WorkerThread, this);
_initialized = true;
return true;
}
catch (const zmq::error_t& e)
{
TC_LOG_ERROR("araxia.eventbus", "Failed to initialize AraxiaEventBus: {}", e.what());
return false;
}
}
void AraxiaEventBus::Shutdown()
{
if (!_initialized)
return;
TC_LOG_INFO("araxia.eventbus", "Shutting down AraxiaEventBus...");
_running = false;
if (_workerThread.joinable())
_workerThread.join();
_subscriber.reset();
_publisher.reset();
_context.reset();
_initialized = false;
TC_LOG_INFO("araxia.eventbus", "AraxiaEventBus shutdown complete");
}
std::string EventContext::ToJson() const
{
std::ostringstream ss;
ss << "{\"map_id\":" << MapId
<< ",\"instance_id\":" << InstanceId
<< ",\"difficulty\":" << Difficulty
<< ",\"zone_id\":" << ZoneId
<< ",\"type\":" << static_cast<int>(Type) << "}";
return ss.str();
}
std::string AraxiaEventBus::BuildEnvelope(const std::string& topic, const EventContext& context,
const std::string& payload)
{
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
std::ostringstream ss;
ss << "{\"v\":1"
<< ",\"topic\":\"" << topic << "\""
<< ",\"ts\":" << ms
<< ",\"source\":\"worldserver\""
<< ",\"context\":" << context.ToJson()
<< ",\"payload\":" << payload << "}";
return ss.str();
}
void AraxiaEventBus::Publish(const std::string& topic, const std::string& jsonPayload)
{
EventContext ctx;
Publish(topic, ctx, jsonPayload);
}
void AraxiaEventBus::Publish(const std::string& topic, const EventContext& context,
const std::string& jsonPayload)
{
if (!_initialized)
return;
std::string envelope = BuildEnvelope(topic, context, jsonPayload);
// Queue for worker thread
{
std::lock_guard<std::mutex> lock(_outboundMutex);
_outboundQueue.push({topic, envelope});
}
}
std::string AraxiaEventBus::ContentTypeToPrefix(ContentType type)
{
switch (type)
{
case ContentType::World: return "world";
case ContentType::Dungeon: return "dungeon";
case ContentType::Raid: return "raid";
case ContentType::Battleground: return "bg";
case ContentType::Arena: return "arena";
default: return "world";
}
}
void AraxiaEventBus::PublishSpawnEvent(ContentType type, bool isCreate, uint64 guid, uint32 entry,
uint32 mapId, uint32 instanceId, float x, float y, float z)
{
std::string prefix = ContentTypeToPrefix(type);
std::string topic = prefix + ".spawn." + (isCreate ? "create" : "delete");
EventContext ctx;
ctx.MapId = mapId;
ctx.InstanceId = instanceId;
ctx.Type = type;
std::ostringstream payload;
payload << "{\"guid\":" << guid
<< ",\"entry\":" << entry
<< ",\"x\":" << x
<< ",\"y\":" << y
<< ",\"z\":" << z << "}";
Publish(topic, ctx, payload.str());
}
void AraxiaEventBus::PublishEncounterEvent(ContentType type, const std::string& eventType,
uint32 encounterId, uint32 mapId, uint32 instanceId,
const std::string& extraJson)
{
std::string prefix = ContentTypeToPrefix(type);
std::string topic = prefix + ".encounter." + eventType;
EventContext ctx;
ctx.MapId = mapId;
ctx.InstanceId = instanceId;
ctx.Type = type;
std::ostringstream payload;
payload << "{\"encounter_id\":" << encounterId;
// Merge extra JSON if provided
if (extraJson.length() > 2) // More than just "{}"
{
// Strip leading { from extraJson and append
payload << "," << extraJson.substr(1);
}
else
{
payload << "}";
}
Publish(topic, ctx, payload.str());
}
void AraxiaEventBus::PublishPlayerEvent(const std::string& eventType, uint64 playerGuid,
const std::string& playerName, const EventContext& context)
{
std::string prefix = ContentTypeToPrefix(context.Type);
std::string topic = prefix + ".player." + eventType;
std::ostringstream payload;
payload << "{\"player_guid\":" << playerGuid
<< ",\"player_name\":\"" << playerName << "\"}";
Publish(topic, context, payload.str());
}
void AraxiaEventBus::Subscribe(const std::string& topicPrefix, EventHandler handler)
{
std::lock_guard<std::mutex> lock(_handlersMutex);
_handlers.push_back({topicPrefix, handler});
// Also subscribe at ZMQ level
if (_subscriber)
{
try
{
_subscriber->set(zmq::sockopt::subscribe, topicPrefix);
}
catch (const zmq::error_t& e)
{
TC_LOG_ERROR("araxia.eventbus", "Failed to subscribe to {}: {}", topicPrefix, e.what());
}
}
}
void AraxiaEventBus::Unsubscribe(const std::string& topicPrefix)
{
std::lock_guard<std::mutex> lock(_handlersMutex);
_handlers.erase(
std::remove_if(_handlers.begin(), _handlers.end(),
[&topicPrefix](const auto& pair) { return pair.first == topicPrefix; }),
_handlers.end());
// Also unsubscribe at ZMQ level
if (_subscriber)
{
try
{
_subscriber->set(zmq::sockopt::unsubscribe, topicPrefix);
}
catch (const zmq::error_t& e)
{
TC_LOG_ERROR("araxia.eventbus", "Failed to unsubscribe from {}: {}", topicPrefix, e.what());
}
}
}
void AraxiaEventBus::WorkerThread()
{
TC_LOG_INFO("araxia.eventbus", "Event bus worker thread started");
while (_running)
{
// Process outbound messages
{
std::lock_guard<std::mutex> lock(_outboundMutex);
while (!_outboundQueue.empty())
{
auto& msg = _outboundQueue.front();
try
{
// ZMQ PUB sends topic as first frame
std::string fullMsg = msg.first + " " + msg.second;
zmq::message_t zmqMsg(fullMsg.data(), fullMsg.size());
_publisher->send(zmqMsg, zmq::send_flags::none);
}
catch (const zmq::error_t& e)
{
TC_LOG_ERROR("araxia.eventbus", "Failed to publish message: {}", e.what());
}
_outboundQueue.pop();
}
}
// Process inbound messages
ProcessInboundMessages();
// Small sleep to prevent busy-waiting
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
TC_LOG_INFO("araxia.eventbus", "Event bus worker thread stopped");
}
void AraxiaEventBus::ProcessInboundMessages()
{
zmq::message_t msg;
while (true)
{
try
{
auto result = _subscriber->recv(msg, zmq::recv_flags::dontwait);
if (!result)
break;
std::string data(static_cast<char*>(msg.data()), msg.size());
// Parse topic from message (format: "topic payload")
size_t spacePos = data.find(' ');
if (spacePos != std::string::npos)
{
std::string topic = data.substr(0, spacePos);
std::string payload = data.substr(spacePos + 1);
// Queue for main thread processing
std::lock_guard<std::mutex> lock(_inboundMutex);
_inboundQueue.push({topic, payload});
}
}
catch (const zmq::error_t& e)
{
if (e.num() != EAGAIN)
TC_LOG_ERROR("araxia.eventbus", "Error receiving message: {}", e.what());
break;
}
}
}
void AraxiaEventBus::Update(uint32 /*diff*/)
{
if (!_initialized)
return;
// Process inbound messages on main thread
std::vector<std::pair<std::string, std::string>> messages;
{
std::lock_guard<std::mutex> lock(_inboundMutex);
while (!_inboundQueue.empty())
{
messages.push_back(_inboundQueue.front());
_inboundQueue.pop();
}
}
// Dispatch to handlers
std::lock_guard<std::mutex> lock(_handlersMutex);
for (const auto& msg : messages)
{
for (const auto& handler : _handlers)
{
// Check if topic matches handler prefix
if (msg.first.compare(0, handler.first.length(), handler.first) == 0)
{
try
{
handler.second(msg.first, msg.second);
}
catch (const std::exception& e)
{
TC_LOG_ERROR("araxia.eventbus", "Handler exception for topic {}: {}",
msg.first, e.what());
}
}
}
}
}
ContentType AraxiaEventBus::GetContentTypeForMap(uint32 mapId)
{
// Use callback to resolve map type (set by game code with DB2 access)
// This decouples the eventbus library from game-specific DB2 stores
if (_mapTypeResolver)
return _mapTypeResolver(mapId);
// Fallback to World if no resolver set
return ContentType::World;
}

View File

@@ -0,0 +1,167 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARAXIA_EVENT_BUS_H
#define ARAXIA_EVENT_BUS_H
/*
* AraxiaEventBus - ZeroMQ-based pub/sub event bus for server events
*
* This provides real-time event publishing for:
* - Spawn/despawn events (world, dungeon, raid, bg contexts)
* - Encounter events (start, end, spell casts, phase transitions)
* - Player events (login, logout, death)
* - Command subscription (reload, announce, etc.)
*
* Events are published with context-aware topics:
* world.spawn.create, dungeon.encounter.start, raid.encounter.spell_cast, etc.
*
* Message format is JSON with envelope:
* { "v": 1, "topic": "...", "ts": ..., "source": "worldserver", "context": {...}, "payload": {...} }
*
* See: openspec/changes/add-zeromq-event-bus/design.md
*/
#include "Define.h"
#include <string>
#include <functional>
#include <memory>
#include <thread>
#include <atomic>
#include <mutex>
#include <queue>
// Forward declare ZMQ types to avoid including zmq.hpp in header
namespace zmq {
class context_t;
class socket_t;
}
// Content type for topic routing
enum class ContentType : uint8
{
World = 0, // Open world (non-instanced)
Dungeon = 1, // 5-man dungeon
Raid = 2, // Raid instance
Battleground = 3, // PvP battleground
Arena = 4 // Arena
};
// Event context for filtering
struct TC_GAME_API EventContext
{
uint32 MapId = 0;
uint32 InstanceId = 0;
uint32 Difficulty = 0;
uint32 ZoneId = 0;
ContentType Type = ContentType::World;
std::string ToJson() const;
};
// Subscription handler callback
using EventHandler = std::function<void(const std::string& topic, const std::string& payload)>;
// Map type resolver callback - set by game code to provide DB2 lookup
using MapTypeResolver = std::function<ContentType(uint32 mapId)>;
class TC_GAME_API AraxiaEventBus
{
public:
static AraxiaEventBus* Instance();
// Lifecycle
bool Initialize(const std::string& pubEndpoint, const std::string& subEndpoint);
void Shutdown();
bool IsInitialized() const { return _initialized; }
// Publishing - generic
void Publish(const std::string& topic, const std::string& jsonPayload);
void Publish(const std::string& topic, const EventContext& context, const std::string& jsonPayload);
// Publishing - typed helpers
void PublishSpawnEvent(ContentType type, bool isCreate, uint64 guid, uint32 entry,
uint32 mapId, uint32 instanceId, float x, float y, float z);
void PublishEncounterEvent(ContentType type, const std::string& eventType,
uint32 encounterId, uint32 mapId, uint32 instanceId,
const std::string& extraJson = "{}");
void PublishPlayerEvent(const std::string& eventType, uint64 playerGuid,
const std::string& playerName, const EventContext& context);
// Subscribing
void Subscribe(const std::string& topicPrefix, EventHandler handler);
void Unsubscribe(const std::string& topicPrefix);
// Processing - call from world update loop
void Update(uint32 diff);
// Utility
static std::string ContentTypeToPrefix(ContentType type);
ContentType GetContentTypeForMap(uint32 mapId);
// Set map type resolver (called from game code with DB2 access)
void SetMapTypeResolver(MapTypeResolver resolver) { _mapTypeResolver = resolver; }
private:
AraxiaEventBus();
~AraxiaEventBus();
// Prevent copying
AraxiaEventBus(const AraxiaEventBus&) = delete;
AraxiaEventBus& operator=(const AraxiaEventBus&) = delete;
// Build message envelope
std::string BuildEnvelope(const std::string& topic, const EventContext& context,
const std::string& payload);
// Background worker for ZMQ I/O
void WorkerThread();
void ProcessInboundMessages();
// ZMQ resources
std::unique_ptr<zmq::context_t> _context;
std::unique_ptr<zmq::socket_t> _publisher;
std::unique_ptr<zmq::socket_t> _subscriber;
// Worker thread
std::thread _workerThread;
std::atomic<bool> _running{false};
std::atomic<bool> _initialized{false};
// Outbound message queue (main thread -> worker)
std::mutex _outboundMutex;
std::queue<std::pair<std::string, std::string>> _outboundQueue;
// Inbound message queue (worker -> main thread)
std::mutex _inboundMutex;
std::queue<std::pair<std::string, std::string>> _inboundQueue;
// Subscription handlers
std::mutex _handlersMutex;
std::vector<std::pair<std::string, EventHandler>> _handlers;
// Endpoints
std::string _pubEndpoint;
std::string _subEndpoint;
// Map type resolver callback
MapTypeResolver _mapTypeResolver;
};
#define sAraxiaEventBus AraxiaEventBus::Instance()
#endif // ARAXIA_EVENT_BUS_H

View File

@@ -0,0 +1,44 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AraxiaEventBusConfig.h"
#include "Config.h"
#include "Log.h"
using namespace std::string_view_literals;
AraxiaEventBusConfig* AraxiaEventBusConfig::Instance()
{
static AraxiaEventBusConfig instance;
return &instance;
}
void AraxiaEventBusConfig::LoadConfig()
{
_publishEndpoint = sConfigMgr->GetStringDefault("Araxia.EventBus.PublishEndpoint"sv, "tcp://*:5555"sv);
_subscribeEndpoint = sConfigMgr->GetStringDefault("Araxia.EventBus.SubscribeEndpoint"sv, "tcp://127.0.0.1:5556"sv);
_enableSpawnEvents = sConfigMgr->GetBoolDefault("Araxia.EventBus.EnableSpawnEvents"sv, true);
_enableEncounterEvents = sConfigMgr->GetBoolDefault("Araxia.EventBus.EnableEncounterEvents"sv, true);
_enablePlayerEvents = sConfigMgr->GetBoolDefault("Araxia.EventBus.EnablePlayerEvents"sv, true);
TC_LOG_INFO("araxia.eventbus", "EventBus config loaded:");
TC_LOG_INFO("araxia.eventbus", " PublishEndpoint: {}", _publishEndpoint);
TC_LOG_INFO("araxia.eventbus", " SubscribeEndpoint: {}", _subscribeEndpoint);
TC_LOG_INFO("araxia.eventbus", " SpawnEvents: {}", _enableSpawnEvents ? "enabled" : "disabled");
TC_LOG_INFO("araxia.eventbus", " EncounterEvents: {}", _enableEncounterEvents ? "enabled" : "disabled");
TC_LOG_INFO("araxia.eventbus", " PlayerEvents: {}", _enablePlayerEvents ? "enabled" : "disabled");
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARAXIA_EVENT_BUS_CONFIG_H
#define ARAXIA_EVENT_BUS_CONFIG_H
/*
* AraxiaEventBusConfig - Configuration loading for ZeroMQ event bus
*
* Reads settings from worldserver.conf:
* Araxia.EventBus.PublishEndpoint
* Araxia.EventBus.SubscribeEndpoint
* Araxia.EventBus.EnableSpawnEvents
* Araxia.EventBus.EnableEncounterEvents
* Araxia.EventBus.EnablePlayerEvents
*/
#include "Define.h"
#include <string>
class TC_GAME_API AraxiaEventBusConfig
{
public:
static AraxiaEventBusConfig* Instance();
// Load configuration from worldserver.conf
void LoadConfig();
// Getters
std::string GetPublishEndpoint() const { return _publishEndpoint; }
std::string GetSubscribeEndpoint() const { return _subscribeEndpoint; }
bool IsSpawnEventsEnabled() const { return _enableSpawnEvents; }
bool IsEncounterEventsEnabled() const { return _enableEncounterEvents; }
bool IsPlayerEventsEnabled() const { return _enablePlayerEvents; }
private:
AraxiaEventBusConfig() = default;
std::string _publishEndpoint = "tcp://*:5555";
std::string _subscribeEndpoint = "tcp://127.0.0.1:5556";
bool _enableSpawnEvents = true;
bool _enableEncounterEvents = true;
bool _enablePlayerEvents = true;
};
#define sAraxiaEventBusConfig AraxiaEventBusConfig::Instance()
#endif // ARAXIA_EVENT_BUS_CONFIG_H

View File

@@ -0,0 +1,37 @@
# This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# AraxiaEventBus - ZeroMQ-based event bus for server events
# See: openspec/changes/add-zeromq-event-bus/
set(EVENTBUS_SOURCES
AraxiaEventBus.cpp
AraxiaEventBus.h
AraxiaEventBusConfig.cpp
AraxiaEventBusConfig.h
)
add_library(araxia-eventbus STATIC
${EVENTBUS_SOURCES})
target_include_directories(araxia-eventbus
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(araxia-eventbus
PUBLIC
trinity-core-interface
common
PRIVATE
zeromq)
set_target_properties(araxia-eventbus
PROPERTIES
FOLDER "server/araxiaonline")

View File

@@ -25,6 +25,8 @@
#include "LuaEngine/ElunaConfig.h"
#include "LuaEngine/ElunaLoader.h"
#include "AraxiaMCPServer.h"
#include "AraxiaEventBus.h"
#include "AraxiaEventBusConfig.h"
#include "AccountMgr.h"
#include "AchievementMgr.h"
#include "AreaTriggerDataStore.h"
@@ -191,6 +193,9 @@ World::~World()
///- Shutdown Araxia MCP Server
sMCPServer->Shutdown();
///- Shutdown Araxia Event Bus (ZeroMQ)
sAraxiaEventBus->Shutdown();
///- Empty the kicked session set
while (!m_sessions.empty())
{
@@ -1277,6 +1282,35 @@ bool World::SetInitialWorldSettings()
sElunaLoader->LoadScripts();
TC_LOG_INFO("server.loading", "Eluna initialization complete");
///- Initialize Araxia Event Bus (ZeroMQ)
TC_LOG_INFO("server.loading", "Initializing Araxia Event Bus...");
sAraxiaEventBusConfig->LoadConfig();
if (!sAraxiaEventBus->Initialize(
sAraxiaEventBusConfig->GetPublishEndpoint(),
sAraxiaEventBusConfig->GetSubscribeEndpoint()))
{
TC_LOG_ERROR("server.loading", "Failed to initialize Araxia Event Bus - continuing without ZMQ events");
}
else
{
// Set up map type resolver using DB2 stores (eventbus library doesn't have direct access)
sAraxiaEventBus->SetMapTypeResolver([](uint32 mapId) -> ContentType {
MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
if (!mapEntry)
return ContentType::World;
if (mapEntry->IsRaid())
return ContentType::Raid;
if (mapEntry->IsNonRaidDungeon())
return ContentType::Dungeon;
if (mapEntry->IsBattleground())
return ContentType::Battleground;
if (mapEntry->IsBattleArena())
return ContentType::Arena;
return ContentType::World;
});
TC_LOG_INFO("server.loading", "Araxia Event Bus initialized");
}
///- Initialize Allowed Security Level
LoadDBAllowedSecurityLevel();
@@ -2178,6 +2212,9 @@ void World::Update(uint32 diff)
///- Update Araxia systems (MCP player sessions, etc.)
sAraxiaCore->Update(diff);
///- Update Araxia Event Bus (process inbound ZMQ messages)
sAraxiaEventBus->Update(diff);
///- Update the different timers
for (int i = 0; i < WUPDATE_COUNT; ++i)
{

View File

@@ -31,6 +31,7 @@
# NETWORK CONFIG
# CONSOLE AND REMOTE ACCESS
# CHARACTER DELETE OPTIONS
# ARAXIA EVENT BUS (ZEROMQ)
# CUSTOM SERVER OPTIONS
# AUCTION HOUSE BOT SETTINGS
# AUCTION HOUSE BOT ITEM FINE TUNING
@@ -3291,6 +3292,51 @@ CharDelete.KeepDays = 30
#
###################################################################################################
###################################################################################################
# ARAXIA EVENT BUS (ZEROMQ)
#
# Araxia.EventBus.PublishEndpoint
# Description: ZeroMQ endpoint for publishing events.
# Format: "tcp://*:port" for binding, "tcp://host:port" for connecting.
# Default: "tcp://*:5555"
Araxia.EventBus.PublishEndpoint = "tcp://*:5555"
#
# Araxia.EventBus.SubscribeEndpoint
# Description: ZeroMQ endpoint for subscribing to events (from authserver, API, etc).
# Format: "tcp://host:port" for connecting.
# Default: "tcp://127.0.0.1:5556"
Araxia.EventBus.SubscribeEndpoint = "tcp://127.0.0.1:5556"
#
# Araxia.EventBus.EnableSpawnEvents
# Description: Publish creature spawn/despawn events.
# Default: 1 - (Enabled)
# 0 - (Disabled)
Araxia.EventBus.EnableSpawnEvents = 1
#
# Araxia.EventBus.EnableEncounterEvents
# Description: Publish encounter events (start, end, wipe, spell casts).
# Default: 1 - (Enabled)
# 0 - (Disabled)
Araxia.EventBus.EnableEncounterEvents = 1
#
# Araxia.EventBus.EnablePlayerEvents
# Description: Publish player events (login, logout, death).
# Default: 1 - (Enabled)
# 0 - (Disabled)
Araxia.EventBus.EnablePlayerEvents = 1
#
###################################################################################################
###################################################################################################
# CUSTOM SERVER OPTIONS
#