Regulus Remote is a powerful and flexible server-client communication framework developed in C#. Designed to work seamlessly with the Unity game engine and any other .NET Standard 2.0 compliant environments, it simplifies network communication by enabling servers and clients to interact through interfaces. This object-oriented approach reduces the maintenance cost of protocols and enhances code readability and maintainability.
Key features of Regulus Remote include support for IL2CPP and AOT, making it compatible with various platforms, including Unity WebGL. It provides default TCP connection and serialization mechanisms but also allows for customization to suit specific project needs. The framework supports methods, events, properties, and notifiers, giving developers comprehensive tools to build robust networked applications.
With its stand-alone mode, developers can simulate server-client interactions without a network connection, facilitating development and debugging. Regulus Remote aims to streamline network communication in game development and other applications, enabling developers to focus more on implementing business logic rather than dealing with the complexities of network protocols.
Server and client transfer through the interface, reducing the maintenance cost of the protocol.
- 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.
- 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.
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.
This is a server-client framework, so you need to create three projects : Protocol
, Server
and Client
.
- Visual Studio 2022 17.0.5 above.
- .NET Sdk 5 above.
Create common interface project Protocol.csproj
.
Sample/Protocol>dotnet new classlib
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote" Version="0.1.12.15" />
<PackageReference Include="Regulus.Serialization" Version="0.1.12.12" />
<PackageReference Include="Regulus.Remote.Tools.Protocol.Sources" Version="0.0.0.25">
<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.
Create the server. Server.csproj
Sample/Server>dotnet new console
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote.Server" Version="0.1.12.13" />
<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.RegisterClientBinder(IBinder binder)
{
binder.Binder<IGreeter>(new Greeter());
}
void IBinderProvider.UnregisterClientBinder(IBinder binder)
{
// when client disconnect.
}
void IEntry.Update()
{
// Update
}
}
}
- 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();
}
}
Create Client. Client.csproj
.
Sample/Client>dotnet new console
- Add References
<ItemGroup>
<PackageReference Include="Regulus.Remote.Client" Version="0.1.12.12" />
<ProjectReference Include="..\Protocol\Protocol.csproj" />
</ItemGroup>
- Create Tcp client
namespace Client
{
static async Task 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;
var peer = await set.Connector.Connect(yourEndPoint );
set.Agent.Enable(peer);
// 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.Connector.Disconnect();
set.Agent.Disable();
}
}
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.12.14" />
<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();
}
}
If you want to customize the connection system you can do so in the following way.
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);
}
}
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;
}
}
implement ISerializable
.
namespace Regulus.Remote
{
public interface ISerializable
{
Regulus.Memorys.Buffer Serialize(System.Type type, object instance);
object Deserialize(System.Type type, Regulus.Memorys.Buffer 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; }
}
}
Chat Room
A full example of the application can be found here, including the Unity and Stride3D version. Link