Moon Game Server Framework

repository·master·Indexed 21 days ago

https://github.com/sniper00/moon

A lightweight, high-performance game server framework based on the actor model. Moon utilizes ASIO for scheduling and Lua for logic scripting, providing an asynchronous environment for networking (TCP, UDP/KCP, Websockets, HTTP), database interaction (Redis, PostgreSQL, MongoDB, MySQL), and cross-platform compatibility across Windows, Linux, and MacOS.

Tokens
25.5K
Snippets
83
Records
104
Agent score
70%

What's inside Moon

  1. What is Moon and how does it work?

    master

    Moon is a lightweight game server framework based on the actor model.

    In Moon's architecture, a single worker thread can host one or more actors (services). These actors communicate with each other through message queues. The framework is designed for high performance and cross-platform compatibility (Windows, Linux, MacOS), utilizing ASIO for scheduling and Lua for logic scripting.

    Key features include:

    • Asynchronous Programming: Based on Lua coroutines for socket operations, timers, inter-service communication, inter-process communication, and async drivers for Redis, PostgreSQL, MongoDB, and MySQL.
    • Optimized Networking: Support for TCP, UDP/KCP, Websockets, and HTTP.
    • Built-in Libraries: High-performance Lua JSON, Lua Protobuf, Lua Filesystem, Recast Navigation, and a Lua zset library for ranklists.
  2. How the Actor model architecture works in Moon

    master

    The GuessGame example demonstrates Moon's Actor model architecture by separating concerns into specialized services:

    • service_center: A singleton service that manages the matchmaking process and assigns players to rooms.
    • service_room: An instance-based service that handles the specific game logic within a single room.
    • service_user: A service dedicated to processing messages and state for an individual player.
  3. How `upvaluejoin` enables stateful hot-reloading

    master

    The core mechanism for hot-reloading in Moon is debug.upvaluejoin. This allows a new function to reference the exact same memory location as an old function's upvalue, enabling state sharing between old and new logic.

    Core Concept

    • Update Implementation: The new function executes new code logic.
    • Reference Old State: The new function's upvalue points to the old module's upvalue memory address.
    • No Value Modification: upvaluejoin only changes the reference; it does not change the actual value stored in that memory.

    Workflow

    1. Identify by Name: The system finds the upvalue in the old module using its name.
    2. Connect by Index: It uses debug.upvaluejoin to link the new function's specific index to the old function's specific index. Note that the index position may differ between the old and new versions.

    Example Logic

    If old_function has shared_count at index 1, and new_function has shared_count at index 2:

    -- Connect new_function's 2nd upvalue to old_function's 1st upvalue
    debug.upvaluejoin(
        new_function, 2, 
        old_function, 1
    )

    After this, modifying shared_count in either function affects the same memory location.

    -- Example of the underlying mechanism
    -- Step 1: Collect old upvalue info
    local upvalues = {
        ["shared_count"] = {
            func = old_function,  -- The function owning the upvalue
            index = 1,            -- Position in that function
            id = 0x12345,         -- Unique ID
            value = 100           -- Current value
        }
    }
    
    -- Step 2: Connect using upvaluejoin
    local uvname = "shared_count"
    local old_uv = upvalues[uvname]
    
    debug.upvaluejoin(
        new_function, 2,        -- New function's index
        old_uv.func, old_uv.index  -- Old function and its index
    )
  4. How state is preserved during hot-updates

    master

    The Moon hot-update system is designed to update function behavior (implementation) without resetting or modifying the values of state variables (upvalues).

    When you perform a hot-update:

    • Values remain unchanged: If shared_count was 105 at the moment of the update, it remains 105 in the new version, even if the new code declares local shared_count = 100.
    • Shared memory: All functions (both original and newly added) reference the same memory location for these upvalues. Any modification to a shared variable by a new function will be visible to all other functions.
    • Implementation vs. State: The system updates the code logic inside functions but uses upvaluejoin to connect new function implementations to the existing upvalue memory.
  5. Hot-updating nested functions

    master

    The Moon hot-update system supports recursive updates for nested functions. This includes:

    • Functions stored as upvalues of other functions.
    • Functions stored within tables (e.g., handlers.process = function() ... end).

    When an update occurs, the system uses collect_all_uv to recursively collect all nested functions and their upvalues. If a nested function's implementation changes, the system ensures the references are updated throughout the hierarchy.

    -- v2.lua example of updating nested logic
    local M = {}
    
    local function helper()
        return "new helper"
    end
    
    local handlers = {}
    handlers.process = function()
        return helper() .. " v2"
    end
    
    function M.run()
        return handlers.process()
    end
    
    return M
  6. Rules for writing hot-updateable Lua modules

    master

    To ensure a Lua module can be safely hot-updated using the Moon system, you must follow these five rules:

    1. Preserve old local variable declarations: The new version must declare all local variables from the old module (even if they are unused). The values assigned during declaration in the new version will be ignored; the system will use the actual runtime values from the old module.
    2. New functions can only reference existing upvalues: Any newly added function can reference variables like shared_count or shared_prefix that existed in the old module, but it cannot reference new local variables (e.g., new_counter) introduced in the new version.
    3. Existing functions can modify implementation: You can change the internal logic of a function and add or remove upvalue references, provided those upvalues existed in the old module.
    4. You can add functions, but you cannot delete them: Adding functions is supported, but deleting a function from the module table will cause the hot-update to fail because external code may still hold references to it.
    5. Modules must return a table: The hot-update system relies on updating function references within the module table.
    -- Example of a valid hot-updateable module structure
    local M = {}
    
    -- State variables (upvalues)
    local shared_count = 100
    local shared_prefix = "[Old] "
    
    function M.hello()
        return "Hello, World!"
    end
    
    return M
  7. Communicate between services

    master

    Moon supports several inter-service communication patterns:

    1. Message Passing: Use moon.send(type, service_handle, data) to send fire-and-forget messages.
    2. Request-Response: Use moon.call(type, service_handle, data) within an async block to wait for a response. It returns ok, result.
    3. Service Discovery: Use moon.new_service to define a service and moon.queryservice(name) to find services by name.
    4. Unique Services: When creating a service with moon.new_service, set unique = true to make it a global singleton accessible via moon.queryservice.

    Example: Service A calling Service B

    local moon = require("moon")
    
    -- Define Service B
    local service_b = moon.new_service {
        name = "service_b",
        file = "service_b.lua"
    }
    
    -- Send message to B
    moon.send("lua", service_b, {cmd = "hello"})
    
    -- Request-Response pattern
    moon.async(function()
        local ok, result = moon.call("lua", service_b, {cmd = "ping"})
        if ok then
            print("Service B response:", result.cmd)
        end
    end)
  8. How the Service Lifecycle works

    master

    The Moon framework manages the lifecycle of services through a specific sequence of calls:

    1. Instantiation: The register_func (provided during register_service) is called, returning a std::unique_ptr<service>.
    2. Initialization: The framework calls init(const moon::service_conf& conf). The service must return true to proceed.
    3. Execution: The framework enters a loop where it calls dispatch(moon::message* msg) whenever a message is routed to the service.
    4. Destruction: When the service is removed or the server shuts down, the ~service() destructor is called to release resources.
  9. Multi-round hot-updates

    master

    The system supports multiple successive updates (e.g., v1 $\rightarrow$ v2 $\rightarrow$ v3) using a weak table called origin_functions.

    How it works:

    • Every time hotfix.update is called, the system traces back to the original (v1) version to update the upvalue connections.
    • This ensures that even after many updates, the new functions are correctly linked to the original state variables.
    • Intermediate versions (v2, v3) are managed via weak tables, allowing them to be garbage collected if they are no longer referenced.
  10. How Moon's Actor model and architecture work

    master

    Moon is a lightweight game server framework based on the Actor model.

    Core Mental Model

    • Services as Actors: Every Service is an independent Actor. Services maintain isolated state and do not share memory. Communication between services is strictly handled via message passing through a message queue.
    • Workers: A Worker is a thread unit that executes one or more Services. Each Worker has its own independent message queue and can be bound to specific CPU cores (CPU affinity).
    • Server: The Server is the top-level manager that initializes Workers, manages the lifecycle of Services, handles global timers, and manages environment variables.

    Communication Flow

    Services communicate by sending Message objects. The architecture uses a layered approach: the Network Layer (ASIO) handles protocols (TCP, UDP, KCP, WebSocket, HTTP), which then feed into the Core Components (Server, Worker, Service, Message, Timer) to drive game logic.

    ┌─────────────────────────────────────────────────────────────────┐
    │                         Moon Server                              │
    ├─────────────────────────────────────────────────────────────────┤
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
    │  │   Worker 1  │  │   Worker 2  │  │   Worker N │   ...        │
    │  │  ┌───────┐  │  │  ┌───────┐  │  │  ┌───────┐  │              │
    │  │  │Service│  │  │  │Service│  │  │  │Service│  │              │
    │  │  ├───────┤  │  │  ├───────┤  │  │  ├───────┤  │              │
    │  │  │Service│  │  │  │Service│  │  │  │Service│  │              │
    │  │  └───────┘  │  │  └───────┘  │  │  └───────┘  │              │
    │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘              │
    │         │                │                │                      │
    │         └────────────────┴────────────────┘                      │
    │                          │                                       │
    │                  ┌───────┴───────┐                               │
    │                  │  Message Queue │                               │
    │                  └───────────────┘                               │
    ├─────────────────────────────────────────────────────────────────┤
    │                        Core Components                           │
    │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
    │  │ Server  │ │ Worker  │ │ Service │ │ Message │ │  Timer  │   │
    │  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘   │
    ├─────────────────────────────────────────────────────────────────┤
    │                        Network Layer (ASIO)                      │
    │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
    │  │   TCP   │ │   UDP   │ │   KCP   │ │WebSocket│ │   HTTP  │   │
    │  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘   │
    └─────────────────────────────────────────────────────────────────┘
  11. Supported Network Protocols in Moon

    master

    Moon provides high-performance network communication support via an ASIO-based asynchronous non-blocking I/O layer. The following protocols are supported:

    ProtocolMessage TypeDescription
    TCPPTYPE_SOCKET_TCPReliable connection-oriented protocol
    UDPPTYPE_SOCKET_UDPConnectionless datagram protocol
    KCP-Reliable UDP protocol (optimized for games)
    WebSocketPTYPE_SOCKET_WSFull-duplex Web communication
    MoonSocketPTYPE_SOCKET_MOONMoon's custom protocol