Awesome
Regulus Remote
<!-- [![Discord](https://img.shields.io/discord/101557008930451456.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/uDF8NTp) --> <!-- [![GitHub release](https://img.shields.io/github/release/jiowchern/regulus.svg?style=flat-square)](https://github.com/jiowchern/Regulus/releases)![pre-release](https://img.shields.io/github/v/release/jiowchern/Regulus?include_prereleases) --> <!-- [![Gitter](https://badges.gitter.im/JoinChat.svg)](https://gitter.im/Regulus-Library) -->Introduction
It is a server-client connection framework developed using C# and can be used in Unity game engine and other game engines that are compliant with .
<!-- * Remote Method Invocation * .Net Standard 2.0 base * Compatible with Unity il2cpp * Compatible with Unity WebGL * Customizable connection * Stand-alone mode -->Feature
Server and client transfer through the interface, reducing the maintenance cost of the protocol.
<!-- @startuml package Protocol <<Rectangle>>{ interface IGreeter { +SayHello() } } package Server <<Rectangle>> { class Greeter { +SayHello() } } package Client <<Rectangle>>{ class SomeClass{ {field} IGreeter greeter } } IGreeter --* SomeClass::greeter IGreeter <|.. Greeter note left of Greeter Implement IGreeter end note note right of SomeClass::greeter Use object from server. end note @enduml -->Supports
- Support IL2CPP & AOT.
- Compatible with .Net Standard2.0 or above development environment.
- Tcp connection is provided by default, and any connection can be customized according to your needs.
- Serialization is provided by default, and can be customized.
- Support Unity3D WebGL, provide server-side Websocket, client-side need to implement their own.
Usage
- Definition Interface
IGreeter
.
namespace Protocol
{
public struct HelloRequest
{
public string Name;
}
public struct HelloReply
{
public string Message;
}
public interface IGreeter
{
Regulus.Remote.Value<HelloReply> SayHello(HelloRequest request);
}
}
- Server implemente
IGreeter
.
namespace Server
{
class Greeter : IGreeter
{
Regulus.Remote.Value<HelloReply> SayHello(HelloRequest request)
{
return new HelloReply() { Message = $"Hello {request.Name}." };
}
}
}
- Use
IBinder.Bind
to send theIGreeter
to the client.
namespace Server
{
public class Entry
{
readonly Greeter _Greeter;
readonly Regulus.Remote.IBinder _Binder;
readonly Regulus.Remote.ISoul _GreeterSoul;
public Entry(Regulus.Remote.IBinder binder)
{
_Greeter = new Greeter();
_Binder = binder;
// bind to client.
_GreeterSoul = binder.Bind<IGreeter>(_Greeter);
}
public void Dispose()
{
_Binder.Unbind(_GreeterSoul);
}
}
}
- Client uses
IAgent.QueryNotifier
to obtainIGreeter
.
namespace Client
{
class Entry
{
public Entry(Regulus.Remote.IAgent agent)
{
agent.QueryNotifier<IGreeter>().Supply += _AddGreeter;
agent.QueryNotifier<IGreeter>().Unsupply += _RemoveGreeter;
}
async void _AddGreeter(IGreeter greeter)
{
// Having received the greeter from the server,
// begin to implement the following code.
var reply = await greeter.SayHello(new HelloRequest() {Name = "my"});
}
void _RemoveGreeter(IGreeter greeter)
{
// todo: The server has canceled the greeter.
}
}
}
After completing the above steps, the server and client can communicate through the interface to achieve object-oriented development as much as possible.
Specification
Interface
In addition to the above example IGreeter.SayHello
, there are a total of four ways to ...
Serialization
For the types that can be serialized, see Regulus.Serialization
instructions.
Getting Started
This is a server-client framework, so you need to create three projects : Protocol
, Server
and Client
.
Requirements
- Visual Studio 2022 17.0.5 above.
- .NET Sdk 5 above.
Protocol Project
Create common interface project Protocol.csproj
.
Sample/Protocol>dotnet new classlib
<!-- Add references to **Protocol.csproj**. -->
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote" Version="0.1.11.12" />
<PackageReference Include="Regulus.Serialization" Version="0.1.11.12" />
<PackageReference Include="Regulus.Remote.Tools.Protocol.Sources" Version="0.0.0.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
- Add interface,
IGreeter.cs
namespace Protocol
{
public interface IGreeter
{
Regulus.Remote.Value<string> SayHello(string request);
}
}
- Add
ProtocolCreater.cs
.
namespace Protocol
{
public static partial class ProtocolCreater
{
public static Regulus.Remote.IProtocol Create()
{
Regulus.Remote.IProtocol protocol = null;
_Create(ref protocol);
return protocol;
}
/*
Create a partial method as follows.
*/
[Regulus.Remote.Protocol.Creater]
static partial void _Create(ref Regulus.Remote.IProtocol protocol);
}
}
This step is to generate the generator for the IProtocol
, which is an important component of the framework and is needed for communication between the server and the client.
Note
As shown in the code above, Add
Regulus.Remote.Protocol
attribute to the method you want to getIProtocol
, the method specification must bestatic partial void Method(ref Regulus.Remote.IProtocol)
, otherwise it will not pass compilation.
Server Project
Create the server. Server.csproj
Sample/Server>dotnet new console
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote.Server" Version="0.1.11.12" />
<ProjectReference Include="..\Protocol\Protocol.csproj" />
</ItemGroup>
- Instantiate
IGreeter
namespace Server
{
public class Greeter : Protocol.IGreeter
{
Regulus.Remote.Value<string> SayHello(string request)
{
// Return the received message
return $"echo:{request}";
}
}
}
- The server needs an entry point to start the environment , creating an entry point that inherits from
Regulus.Remote.IEntry
.Entry.cs
namespace Server
{
public class Entry : Regulus.Remote.IEntry
{
void IBinderProvider.AssignBinder(IBinder binder)
{
binder.Binder<IGreeter>(new Greeter());
}
}
}
- Create Tcp service
namespace Server
{
static void Main(string[] args)
{
// Get IProtocol with ProtocolCreater
var protocol = Protocol.ProtocolCreater.Create();
// Create Service
var entry = new Entry();
var set = Regulus.Remote.Server.Provider.CreateTcpService(entry, protocol);
int yourPort = 0;
set.Listener.Bind(yourPort);
// Close service
set.Listener.Close();
set.Service.Dispose();
}
}
Client Project
Create Client. Client.csproj
.
Sample/Client>dotnet new console
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote.Client" Version="0.1.11.12" />
<ProjectReference Include="..\Protocol\Protocol.csproj" />
</ItemGroup>
- Create Tcp client
namespace Client
{
static void Main(string[] args)
{
// Get IProtocol with ProtocolCreater
var protocol = Protocol.ProtocolCreater.Create();
var set = Regulus.Remote.Client.Provider.CreateTcpAgent(protocol);
bool stop = false;
var task = System.Threading.Tasks.Task.Run(() =>
{
while (!stop)
{
set.Agent.Update();
}
});
// Start Connecting
EndPoint yourEndPoint = null;
set.Connecter.Connect(yourEndPoint ).Wait();
// SupplyEvent ,Receive add IGreeter.
set.Agent.QueryNotifier<Protocol.IGreeter>().Supply += greeter =>
{
greeter.SayHello("hello");
};
// SupplyEvent ,Receive remove IGreeter.
set.Agent.QueryNotifier<Protocol.IGreeter>().Unsupply += greeter =>
{
};
// Close
stop = true;
task.Wait();
set.Connecter.Disconnect();
set.Agent.Dispose();
}
}
Standalone mode
In order to facilitate development and debugging, a standalone mode is provided to run the system without a connection.
Sample/Standalone>dotnet new console
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote.Standalone" Version="0.1.11.12" />
<ProjectReference Include="..\Protocol\Protocol.csproj" />
<ProjectReference Include="..\Server\Server.csproj" />
</ItemGroup>
- Create standlone service
namespace Standalone
{
static void Main(string[] args)
{
// Get IProtocol with ProtocolCreater
var protocol = Protocol.ProtocolCreater.Create();
// Create service
var entry = new Entry();
var service = Regulus.Remote.Standalone.Provider.CreateService(entry , protocol);
var agent = service.Create();
bool stop = false;
var task = System.Threading.Tasks.Task.Run(() =>
{
while (!stop)
{
agent.Update();
}
});
agent.QueryNotifier<Protocol.IGreeter>().Supply += greeter =>
{
greeter.SayHello("hello");
};
agent.QueryNotifier<Protocol.IGreeter>().Unsupply += greeter =>
{
};
// Close
stop = true;
task.Wait();
agent.Dispose();
service.Dispose();
}
}
Custom Connection
If you want to customize the connection system you can do so in the following way.
Client
Create a connection use CreateAgent
and implement the interface IStreamable
.
var protocol = Protocol.ProtocolCreater.Create();
IStreamable stream = null ;// todo: Implementation Interface IStreamable
var service = Regulus.Remote.Client.CreateAgent(protocol , stream) ;
implement IStreamable
.
using Regulus.Remote;
namespace Regulus.Network
{
public interface IStreamable
{
/// <summary>
/// Receive data streams.
/// </summary>
/// <param name="buffer">Stream instance.</param>
/// <param name="offset">Start receiving position.</param>
/// <param name="count">Count of byte received.</param>
/// <returns>Actual count of byte received.</returns>
IWaitableValue<int> Receive(byte[] buffer, int offset, int count);
/// <summary>
/// Send data streams.
/// </summary>
/// <param name="buffer">Stream instance.</param>
/// <param name="offset">Start send position.</param>
/// <param name="count">Count of byte send.</param>
/// <returns>Actual count of byte send.</returns>
IWaitableValue<int> Send(byte[] buffer, int offset, int count);
}
}
Server
Create a service use CreateService
and implement the interface IListenable
.
var protocol = Protocol.ProtocolCreater.Create();
var entry = new Entry();
IListenable listener = null; // todo: Implementation Interface IListenable
var service = Regulus.Remote.Server.CreateService(entry , protocol , listener) ;
implement IListenable
.
namespace Regulus.Remote.Soul
{
public interface IListenable
{
// When connected
event System.Action<Network.IStreamable> StreamableEnterEvent;
// When disconnected
event System.Action<Network.IStreamable> StreamableLeaveEvent;
}
}
Custom Serialization
implement ISerializable
.
namespace Regulus.Remote
{
public interface ISerializable
{
byte[] Serialize(System.Type type, object instance);
object Deserialize(System.Type type, byte[] buffer);
}
}
and bring it to the server CreateTcpService
.
var protocol = Protocol.ProtocolCreater.Create();
var entry = new Entry();
ISerializable yourSerializer = null;
var service = Regulus.Remote.Server.CreateTcpService(entry , protocol , yourSerializer) ;
and bring it to the client CreateTcpAgent
.
var protocol = Protocol.ProtocolCreater.Create();
ISerializable yourSerializer = null ;
var service = Regulus.Remote.Client.CreateTcpAgent(protocol , yourSerializer) ;
If need to know what types need to be serialized can refer Regulus.Remote.IProtocol.SerializeTypes
.
namespace Regulus.Remote
{
public interface IProtocol
{
// What types need to be serialized.
System.Type[] SerializeTypes { get; }
System.Reflection.Assembly Base { get; }
EventProvider GetEventProvider();
InterfaceProvider GetInterfaceProvider();
MemberMap GetMemberMap();
byte[] VerificationCode { get; }
}
}
Example
Chat Room
A full example of the application can be found here, including the Unity and Stride3D version. Link