Skip to content

Commit

Permalink
Merge pull request #39 from ImNotAVirus/documentation
Browse files Browse the repository at this point in the history
Ready for v0.1.1
  • Loading branch information
ImNotAVirus authored Aug 20, 2023
2 parents e0f9fb2 + e0f0e28 commit a75697d
Show file tree
Hide file tree
Showing 66 changed files with 2,244 additions and 511 deletions.
7 changes: 4 additions & 3 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ inputs =
locals_without_parens = [
field: 2,
field: 3,
packet: 1,
packet: 2,
packet: 3
defpacket: 1,
defpacket: 2,
defpacket: 3,
import_packets: 1
]

[
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name: Elixir CI
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main

permissions:
contents: read
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# TODO
# Changelog

## 0.1.0

Pre-release, this is not a production ready release !
56 changes: 30 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
# ElvenGard.Network

[![Build Status](https://github.com/ImNotAVirus/elvengard_network/actions/workflows/elixir.yml/badge.svg?branch=master)](https://github.com/ImNotAVirus/elvengard_network/actions/workflows/elixir.yml)
[![Coverage Status](https://coveralls.io/repos/github/ImNotAVirus/elvengard_network/badge.svg?branch=master)](https://coveralls.io/github/ImNotAVirus/elvengard_network?branch=master)
<!-- MDOC !-->

[![Hex.pm version](https://img.shields.io/hexpm/v/elvengard_network.svg?style=flat)](https://hex.pm/packages/elvengard_network)
[![Hex.pm license](https://img.shields.io/hexpm/l/elvengard_network.svg?style=flat)](https://hex.pm/packages/elvengard_network)
[![Build Status](https://github.com/ImNotAVirus/elvengard_network/actions/workflows/elixir.yml/badge.svg?branch=main)](https://github.com/ImNotAVirus/elvengard_network/actions/workflows/elixir.yml)
[![Coverage Status](https://coveralls.io/repos/github/ImNotAVirus/elvengard_network/badge.svg?branch=main)](https://coveralls.io/github/ImNotAVirus/elvengard_network?branch=main)

## What is ElvenGard

Currently, all independent developers wishing to create a MMORPG type game have already asked themselves the question of how to make the server part easily while being "solid" (minimum crash, latency, ...).
Currently, all independent developers wishing to create a Multiplayer game have already asked themselves the question of how to make the server part easily while being "solid" (minimum crash, latency, ...).
Indeed, for the client part, there are many tools very well designed to realize it (Unity3D, Unreal Engine,...) but for the server part, each game being different there are currently very few solutions to do this work.

This is the goal of this project: make a toolkit to group together different functionalities present in any MMORPG (network part, quests, movements, objects in game, instances, etc...) to prepare bases for the developer so that he doesn't have to dwell on this part which is often tedious.
This is the goal of this ambitious project: make a toolkit to group together different functionalities present in any MMORPG (network part, quests, movements, objects in game, instances, etc...) to prepare bases for the developer so that he doesn't have to dwell on this part which is often tedious.

## Who is this project for ?
## What is ElvenGard.Network

Initially, this project is intended for anyone who wants to create a game without having to re-code the server part from scratch.
It is also intended for people who want to create an emulator for an existing game. For my tests, I use Nostale as an example. So I already have a predefined network protocol and I don't have to code a client. Later, I also plan to test it with World of Warcraft and FlyFF. Since the network protocols of these games are totally different, this will allow me to test the abstraction of the different features of this toolkit.
[ElvenGard.Network](https://github.com/ImNotAVirus/elvengard_network) is a dedicated toolkit designed to streamline and enhance the network aspect of game server development. Built on top of the "ranch" Erlang library, this powerful toolkit provides developers with a robust foundation for creating multiplayer game servers with ease.

## Installation
Key Features:

Currently not [available in Hex](https://hex.pm/docs/publish), you can use it like that:
1. **Specialized Network Handling:** ElvenGard.Network is solely focused on handling the complexities of networking in multiplayer game servers. It offers a high-performance solution optimized for managing player connections, communication, and data exchange.

```elixir
def deps do
[
{:elvengard_network, github: "imnotavirus/elvengard_network"}
]
end
```
2. **Efficient Protocol Management:** The toolkit simplifies the implementation of network protocols, allowing developers to define and manage packet structures efficiently. This streamlined approach ensures smooth and reliable communication between the game client and server.

3. **Packet Processing Made Easy:** ElvenGard.Network streamlines packet processing, making it straightforward to handle incoming and outgoing data. Developers can effortlessly manage packet reception, interpretation, and response, thereby reducing the burden of low-level network management.

4. **Custom Serialization/Deserialization:** With support for custom data types, ElvenGard.Network enables seamless serialization and deserialization of data. This feature ensures compatibility and efficient data exchange between different components of the game server.

5. **Minimized Latency and Crashes:** By leveraging the "ranch" library, ElvenGard.Network benefits from Erlang's reliability and scalability. This helps minimize latency issues and reduces the risk of crashes, providing a stable networking foundation for multiplayer games.

With ElvenGard.Network handling the intricate network tasks, game developers can focus on creating captivating gameplay, dynamic worlds, and engaging player experiences. By leveraging this specialized toolkit, the journey from conceptualizing a multiplayer game to deploying a robust server becomes significantly smoother and more efficient.

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `elvengard_network` to your list of dependencies in `mix.exs`:
The package can be installed by adding `elvengard_network` to your list of dependencies in `mix.exs`:

```elixir
def deps do
Expand All @@ -38,18 +44,16 @@ def deps do
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/elvengard_network](https://hexdocs.pm/elvengard_network).
The docs can be found at [https://hexdocs.pm/elvengard_network](https://hexdocs.pm/elvengard_network).

**/!\ This application is currently not production ready !**
**/!\ This toolkit is currently not production ready !**

## Projects using ElvenGard

- [Flügel](https://github.com/ImNotAVirus/Flugel-NostaleEmu): Created by the same developer as ElvenGard, this is a [Nostale](http://nostale.com/) emulator
- [Imanity](https://github.com/ImNotAVirus/Imanity-FlyffEmu): Created by the same developer as ElvenGard, this is a [FlyFF](http://flyff.webzen.com) emulator
- [MinecraftEx](https://github.com/ImNotAVirus/elvengard_network/tree/main/examples/minecraft_ex): Located in the `examples` folder of the repository, this is the beginning of a [Minecraft](https://www.minecraft.net) server emulator
- [AvantHeim](https://github.com/ImNotAVirus/AvantHeim): Created by the same developer as ElvenGard, this is a [NosTale](https://gameforge.com/en-US/play/nostale) server emulator

## Contributing

Currently developing this project, I will often open pull-requests. Any review is welcome.
Also, feel free to fork the repository.
I'm currently developing this project. Any review or PR is welcome.
Also, feel free to fork the repository and contribute.
14 changes: 8 additions & 6 deletions TODOLIST.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# TODOLIST
# Todo list

- [ ] Fix Endpoint start_link and child_spec
- [ ] Serializers for `ElvenGard.Network.Socket`
- [ ] Rewrite the doc for `ElvenGard.Network.Socket`
- [ ] Use `c:handle_error/2` in `ElvenGard.Network.Endpoint.Protocol`
- [ ] Check if the lib is working in clustered mode
- Abstract the message handler and provide a generic way to handle all transport (ranch, gen_tcp, gen_udp, ...)
- Refacto ElvenGard.Network.PacketSerializer
- Refacto ElvenGard.Network.Endpoint.Protocol
- Document all `use` according to the bests practices
- Add telemetry
- use @derive for packet serialization/deserialization instead of `@serializable` and `@deserializable` ??
- mix task `elven_network.new` to create a project structure
4 changes: 2 additions & 2 deletions examples/echo_server/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ config :echo_server, EchoServer.Endpoint.Protocol,
# Here, the packet handler is not needed because we bypass the packet
# handling by returning `ignore` in `handle_message/2`
packet_handler: :unset,
# Here we are not using packet coder/decoder.
# Here we are not using the network encoder/decoder.
# We send raw packets
packet_codec: :unset
network_codec: :unset
5 changes: 5 additions & 0 deletions examples/login_server/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:elvengard_network]
]
2 changes: 2 additions & 0 deletions examples/login_server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_build/
deps/
11 changes: 11 additions & 0 deletions examples/login_server/config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Config

config :login_server, LoginServer.Endpoint,
listener_name: :login_server,
transport: :ranch_tcp,
transport_opts: [ip: "127.0.0.1", port: 3000],
protocol: LoginServer.Endpoint.Protocol

config :login_server, LoginServer.Endpoint.Protocol,
packet_handler: LoginServer.Endpoint.PacketHandler,
network_codec: LoginServer.Endpoint.NetworkCodec
5 changes: 5 additions & 0 deletions examples/login_server/lib/login_server.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule LoginServer do
@moduledoc """
Documentation for `LoginServer`.
"""
end
19 changes: 19 additions & 0 deletions examples/login_server/lib/login_server/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule LoginServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false

use Application

@impl true
def start(_type, _args) do
children = [
LoginServer.Endpoint
]

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: LoginServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
22 changes: 22 additions & 0 deletions examples/login_server/lib/login_server/client_packets.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule LoginServer.ClientPackets do
@moduledoc """
Documentation for LoginServer.ClientPackets
"""

use ElvenGard.Network.PacketSerializer

alias LoginServer.Types.StringType

## Ping packet

@deserializable true
defpacket "PING", as: PingRequest

## Login packet

@deserializable true
defpacket "LOGIN", as: LoginRequest do
field :username, StringType
field :password, StringType
end
end
18 changes: 18 additions & 0 deletions examples/login_server/lib/login_server/endpoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule LoginServer.Endpoint do
@moduledoc """
Documentation for LoginServer.Endpoint
"""

use ElvenGard.Network.Endpoint, otp_app: :login_server

require Logger

## Callbacks

@impl true
def handle_start(config) do
host = get_in(config, [:transport_opts, :socket_opts, :ip])
port = get_in(config, [:transport_opts, :socket_opts, :port])
Logger.info("LoginServer started on #{:inet.ntoa(host)}:#{port}")
end
end
43 changes: 43 additions & 0 deletions examples/login_server/lib/login_server/endpoint/network_codec.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule LoginServer.Endpoint.NetworkCodec do
@moduledoc """
Documentation for LoginServer.Endpoint.NetworkCodec
"""

@behaviour ElvenGard.Network.NetworkCodec

alias LoginServer.ClientPackets

## Behaviour impls

@impl true
def next(raw, _socket) do
case String.split(raw, "\n", parts: 2) do
[packet] -> {packet, ""}
[packet, rest] -> {packet, rest}
end
end

@impl true
def decode(raw, socket) do
case String.split(raw, " ", parts: 2) do
[packet_id] -> ClientPackets.deserialize(packet_id, "", socket)
[packet_id, params] -> ClientPackets.deserialize(packet_id, params, socket)
end
end

@impl true
def encode(struct, _socket) when is_struct(struct) do
# %LoginSucceed{world_info: %WorldInfo{host: "127.0.0.1", port: 5000}}
struct
# {"SUCCESS", ["127.0.0.1", ":", "5000"]}
|> struct.__struct__.serialize()
# ["SUCCESS", ["127.0.0.1", ":", "5000"]]
|> Tuple.to_list()
# ["SUCCESS", " ", ["127.0.0.1", ":", "5000"]]
|> Enum.intersperse(" ")
# [["SUCCESS", " ", ["127.0.0.1", ":", "5000"]], "\n"]
|> then(&[&1 | "\n"])

# This will be serialized into `SUCCESS 127.0.0.1:5000\n`
end
end
46 changes: 46 additions & 0 deletions examples/login_server/lib/login_server/endpoint/packet_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule LoginServer.Endpoint.PacketHandler do
@moduledoc """
LoginServer.Endpoint.PacketHandler
"""

@behaviour ElvenGard.Network.PacketHandler

alias ElvenGard.Network.Socket

alias LoginServer.ClientPackets.{PingRequest, LoginRequest}
alias LoginServer.PacketViews

## Handlers

@impl true
def handle_packet(%PingRequest{}, socket) do
render = PacketViews.render(:pong_response, %{time: DateTime.utc_now()})
:ok = Socket.send(socket, render)
{:cont, socket}
end

def handle_packet(%LoginRequest{username: username, password: password}, socket) do
render =
if auth_using_db(username, password) do
PacketViews.render(:login_succeed, %{world: get_worlds_from_manager()})
else
PacketViews.render(:login_failed, %{reason: "Bad credentials :/`"})
end

:ok = Socket.send(socket, render)
{:halt, socket}
end

## Fake functions

defp auth_using_db(username, password) do
case {username, password} do
{"admin", "password"} -> true
_ -> false
end
end

defp get_worlds_from_manager() do
%{host: "127.0.0.1", port: 5000}
end
end
35 changes: 35 additions & 0 deletions examples/login_server/lib/login_server/endpoint/protocol.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule LoginServer.Endpoint.Protocol do
@moduledoc """
Documentation for LoginServer.Endpoint.Protocol
"""

use ElvenGard.Network.Endpoint.Protocol

require Logger

alias ElvenGard.Network.Socket

## Callbacks

@impl true
def handle_init(%Socket{} = socket) do
Logger.info("New connection: #{socket.id}")

%Socket{transport: transport, transport_pid: transport_pid} = socket
:ok = transport.setopts(transport_pid, packet: :line, reuseaddr: true)

{:ok, socket}
end

@impl true
def handle_message(message, %Socket{} = socket) do
Logger.debug("New message from #{socket.id}: #{inspect(message)}")
{:ok, socket}
end

@impl true
def handle_halt(reason, %Socket{} = socket) do
Logger.info("#{socket.id} is now disconnected (reason: #{inspect(reason)})")
{:ok, socket}
end
end
23 changes: 23 additions & 0 deletions examples/login_server/lib/login_server/packet_views.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule LoginServer.PacketViews do
@moduledoc """
Documentation for LoginServer.PacketViews
"""

use ElvenGard.Network.View

alias LoginServer.ServerPackets.{PongResponse, LoginFailed, LoginSucceed}
alias LoginServer.SubPackets.WorldInfo

@impl true
def render(:pong_response, %{time: time}) do
%PongResponse{time: time}
end

def render(:login_failed, %{reason: reason}) do
%LoginFailed{reason: reason}
end

def render(:login_succeed, %{world: world}) do
%LoginSucceed{world: %WorldInfo{host: world.host, port: world.port}}
end
end
Loading

0 comments on commit a75697d

Please sign in to comment.