Cy#ã®æ²³åã§ããCy#ã¯ä»å¹Žã®9æã«èšç«ãããã°ããã®äŒç€Ÿã§ããã®åã®éãC#é¢é£ã®éçºãè¡ã£ãŠãããŸããä»åã¯Cy#ãããªãŒãã³ãœãŒã¹ã©ã€ãã©ãªãšããŠãUnityåãã®ãªã¢ã«ã¿ã€ã éä¿¡/APIéä¿¡çµ±åã©ã€ãã©ãªããªãªãŒã¹ããŸããã
GitHub â cysharp/MagicOnion
ããšããš2幎åã«æåã«å
¬éããå®éã«ãªãªãŒã¹ãããã¢ãã€ã«ã²ãŒã ã§ã䜿çšããŠãããã®ã§ãããä»åãªã¢ã«ã¿ã€ã éä¿¡åãæ©èœããããã©ãã·ã¥ã¢ããããŠãæ£åŒå
¬éãšããŸãããããããç¹ã§ã¯ãâæ¢ã«å®çžŸãããâãšãèšããŸããä»åããæ°ããã¹ã¿ãŒããšããããšã§ãããŒãžã§ã³2.0ã§ãã
åºæ¬çãªæ©èœã¯ ãµãŒããŒã¯ã©ã€ã¢ã³ãéã®ã¹ããªãŒãã³ã°RPCãæäŸããŸãããµãŒããŒåŽãC#ãã¯ã©ã€ã¢ã³ãåŽãC#ã§å®è£
ããã¡ãã»ãŒãžã®ãã©ãŒãããã¯LZ4å§çž®ãããMessagePackãéä¿¡ã¯gRPCã«ããHTTP/2ãçšããŠããŸããä»ã®ããã«ãŠã§ã¢ã«ããŠã¯ãããšNode.js(JavaScript)ã«ããSocket.io(WebSocket)ãã€ã¡ãŒãžãšããŠè¿ããããããŸããããŸããåæã«APIãµãŒããŒãšããŠã®æ©èœããµããŒããããããäžè¬ã®ãŠã§ããã¬ãŒã ã¯ãŒã¯çã§ããããŸãã
MagicOnionã¯æé«ã®ããã©ãŒãã³ã¹ãšãC#ãšããŠæè§Šãã®è¯ãã€ã³ã¿ãŒãã§ã€ã¹ã®å®çŸã«æ³šåããŸããã
C#ã«ãã匷ãåä»ããããã€ã³ã¿ãŒãã§ã€ã¹
ãµãŒããŒãšã¯ã©ã€ã¢ã³ãéã§ã¯C#ã®ã€ã³ã¿ãŒãã§ãŒã¹ãå
±æããããšã«ãã£ãŠãã¯ã©ã€ã¢ã³ã->ãµãŒããŒãžã®ã¡ãœããåŒã³åºããšããµãŒããŒ->ã¯ã©ã€ã¢ã³ããžã®ã¡ãœããåŒã³åºãäž¡æ¹ãã匷ãåå®çŸ©ããŸããäŸãšããŠä»¥äžã®ãããªã€ã³ã¿ãŒãã§ã€ã¹ãã¯ã©ã¹ãå
±æãããšããŸãã
// ãµãŒã㌠-> ã¯ã©ã€ã¢ã³ãã®éä¿¡å®çŸ©
public interface IGamingHubReceiver
{
void OnJoin(Player player);
void OnLeave(Player player);
void OnMove(Player player);
}
// ã¯ã©ã€ã¢ã³ã -> ãµãŒããŒã®éä¿¡å®çŸ©
public interface IGamingHub : IStreamingHub<IGamingHub, IGamingHubReceiver>
{
Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation);
Task LeaveAsync();
Task MoveAsync(Vector3 position, Quaternion rotation);
}
// éåä¿¡ã«äœ¿ãã«ã¹ã¿ã ãªããžã§ã¯ã
[MessagePackObject]
public class Player
{
[Key(0)]
public string Name { get; set; }
[Key(1)]
public Vector3 Position { get; set; }
[Key(2)]
public Quaternion Rotation { get; set; }
}
ããããµãŒããŒãšã¯ã©ã€ã¢ã³ãã§å
±æãããšãã©ã¡ãããã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããã ãã§ãæ£ããéä¿¡ãã§ããŸãã
![]()
ãšãããããã«ãäžéèšèªããã®ã³ãŒãçæçãäžèŠã§ãC#ãšããŠèªç¶ã«ïŒè€æ°åŒæ°ãããªããã£ãåãªã©ã䜿ããïŒã¡ãœãããåŒã¶ã ãã§ããããã¯ãŒã¯ãè¶
ããŠã¡ãœããåŒã³åºããã§ããŸãããã¡ãããå
¥åè£å®ãå¹ããŸãã
![]()
å
·äœçãªå®è£
ã¯ã以äžã®ããã«ãªããŸãããµãŒããŒã¯IGamingHubãšããŠå®çŸ©ããã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããŸãã
// ãµãŒããŒå®è£
public class GamingHub : StreamingHubBase<IGamingHub, IGamingHubReceiver>, IGamingHub
{
IGroup room;
Player self;
IInMemoryStorage<Player> storage;
public async Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation)
{
self = new Player() { Name = userName, Position = position, Rotation = rotation };
(room, storage) = await Group.AddAsync(roomName, self);
Broadcast(room).OnJoin(self);
return storage.AllValues.ToArray();
}
public async Task LeaveAsync()
{
await room.RemoveAsync(this.Context);
Broadcast(room).OnLeave(self);
}
public async Task MoveAsync(Vector3 position, Quaternion rotation)
{
self.Position = position;
self.Rotation = rotation;
Broadcast(room).OnMove(self);
}
}
ãã€ã³ãã¯
- å
šãŠéåæïŒæ»ãå€Taskã§async)
- æ»ãå€ãè¿ãããšãåºæ¥ãïŒäŸå€ãçºçããå Žåããããã¯ã©ã€ã¢ã³ãã«äŸå€ãšããŠäŒæ¬ãããŸãïŒ
- Groupã§ã°ã«ãŒã管çããŠBroadcast(group)ã§ã°ã«ãŒãå
ã®ã¯ã©ã€ã¢ã³ãã«éä¿¡
ãšãã£ããšããã«ãªã£ãŠããŸããã¯ã©ã€ã¢ã³ãã®ã»ãã¯IGamingHubReceiverãšããŠå®çŸ©ããã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããããšã§ãµãŒããŒãããããŒããã£ã¹ãããããŒã¿ãåãåããŸãããŸããIGamingHubãã®ãã®ããµãŒããŒãžã®èªåå®è£
ããããããã¯ãŒã¯ã¯ã©ã€ã¢ã³ããšãªã£ãŠããŸãã
public class GamingHubClient : IGamingHubReceiver
{
Dictionary<string, GameObject> players = new Dictionary<string, GameObject>();
// å§è²ããã¡ãœãããç«ãŠãã®ãé¢åãªå Žåã¯ïŒé¢åïŒããããã®ãŸãŸå
¬éãããããŠãå¿è«å¥ã«è¯ãã
IGamingHub client;
public async Task<GameObject> ConnectAsync(Channel grpcChannel, string roomName, string playerName)
{
var client = StreamingHubClient.Connect<IGamingHub, IGamingHubReceiver>(grpcChannel, this);
var roomPlayers = await client.JoinAsync(roomName, playerName, Vector3.zero, Quaternion.identity);
foreach (var player in roomPlayers)
{
(this as IGamingHubReceiver).OnJoin(player);
}
return players[playerName]; // ååã ãã§ããããšãèåŒ±ã®æ¥µã¿ã§ããããŸããµã³ãã«ãªã®ã§ã
}
// ãµãŒããŒãžéãã¡ãœãã矀
public Task LeaveAsync()
{
return client.LeaveAsync();
}
public Task MoveAsync(Vector3 position, Quaternion rotation)
{
return client.MoveAsync(position, rotation);
}
// åŸå§æ«ãããã®
public Task DisposeAsync()
{
return client.DisposeAsync();
}
// æ£åžž/ç°åžžçµäºãç£èŠã§ããããããåŸ
ã£ãŠãªãã©ã€ãããããªã©ã
public Task WaitForDisconnect()
{
return client.WaitForDisconnect();
}
// ãµãŒããŒããBroadcastããããã®ãåä¿¡ããã¡ãœãã
void IGamingHubReceiver.OnJoin(Player player)
{
Debug.Log("Join Player:" + player.Name);
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = player.Name;
cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
players[player.Name] = cube;
}
void IGamingHubReceiver.OnLeave(Player player)
{
Debug.Log("Leave Player:" + player.Name);
if (players.TryGetValue(player.Name, out var cube))
{
GameObject.Destroy(cube);
}
}
void IGamingHubReceiver.OnMove(Player player)
{
Debug.Log("Move Player:" + player.Name);
if (players.TryGetValue(player.Name, out var cube))
{
// ç§»åã«å¯ŸããŠè£å®ãå
¥ã£ãŠããªãã®ã§å¿è«ãã®ãŸãŸã§ã¯ã¯ãŒãããŠããŸããŸã
// ããã«å¯Ÿããè£å©ã¯ïŒçŸç¶ã¯ïŒãªãã®ã§åèªã§è¡ã£ãŠãã ãã
cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
}
}
}
C#ãšããŠå
šãŠã匷ãåå®çŸ©ãããŠããããã
- ã¡ãœããåãåŒæ°ã®å€æŽãªã©ã«å¯ŸããŠã®IDEã®ãªãã¡ã¯ã¿ãªã³ã°è¿œè·¡ãããŠãµãŒããŒ/ã¯ã©ã€ã¢ã³ãåæ¹ã§å¹ã
- å®è£
æŒããªã©ã¯ã³ã³ãã€ã«ãšã©ãŒã«ãã£ãŠèªç¶ã«é²ãããšãåºæ¥ã
- æååãä»ããªãããšã«ããå¹ççãªéä¿¡ãã§ãã(ã¡ãœããåã¯èªåçã«æ°å€ã«ããIDã«å€æãããŠãããããæååã¯éããŸãã)
- intãªã©ããªããã£ãåãèªç¶ã«éããïŒãããŠåºæã®ãªã¯ãšã¹ãã¯ã©ã¹ãªã©ã«ã©ããããå¿
èŠã¯ãªãïŒ
ãšããã¡ãªããããããŸããProtocol Buffers ã䜿ãå Žå .proto (IDL : äžéå®çŸ©) ãã¡ã€ã«ã®ç®¡çããçæãã©ãããããªã©ãå€ãã®åé¡ãçºçããŸãããC#ãã®ãã®ã§ããéããäžåãã®æã®åé¡ã¯èµ·ãããŸããã
ãŒããã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãããã³ã°
RPCãç¹ã«é »åºŠã®é«ãéä¿¡ãè¡ããããªã¢ã«ã¿ã€ã éä¿¡ã§ã¯ãéåä¿¡çšã«ããŒã¿ã倿ããã·ãªã¢ã©ã€ãºåŠçãæ§èœé¢ã§ã®ããã¯ã«ãªãããšããããŸãããMagicOnionã§ã¯ç§ã®éçºããC#æéã®ãã€ããªã·ãªã¢ã©ã€ã¶ã§ããMessagePack for C#ã«ãã£ãŠã·ãªã¢ã©ã€ãºãããããã·ãªã¢ã©ã€ãºåŠçã¯ããã«ããã¯ã«ãªãããŸããããŸããMessagePack for C#ã§ã·ãªã¢ã©ã€ãºã§ãããªããã©ããªåã§ãéãããšãã§ãããšããããŒã¿ã«å¯Ÿããæè»æ§ããæ§èœãšåæã«ç²åŸããŠããŸãã
ãŸããã¯ã©ã€ã¢ã³ã/ãµãŒããŒãå
±ã«C#ã§ãããããå
éšã®ã¡ã¢ãªäžã®ããŒã¿ã¯åäžã¬ã€ã¢ãŠããæåŸ
ã§ãããããšãçãããŠãå€åã®å Žåã«ã·ãªã¢ã©ã€ãº/ãã·ãªã¢ã©ã€ãºåŠçãããã¡ã¢ãªã³ããŒã ãã§ãããã³ã°ããããšãããªãã·ã§ã³ãçšæããŸããã
// Vector3ãªã©ã®Unityã®æšæºstructãšãã®é
åããããã¯ã«ã¹ã¿ã structãšãã®é
åã察å¿å¯èœ
// å³å¯ã«ãµã€ãºãåããããã[StructLayout(LayoutKind.Explicit)]ã§æç€ºããããšãèŠããŸã
public struct CustomStruct
{
public long Id;
public int Hp;
public int Mp;
public byte Status;
}
// ---- åæåäºã«ä»¥äžã®ãããªã³ãŒããç»é²ãã
// ç»é²ããããšã§TãšT[]ããŒããã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãããã³ã°ã«ãªã
UnsafeDirectBlitResolver.Register<CustomStruct>();
// âã®structãšUnityæšæºã®æ§é äœ(Vector2, Rectãªã©)ãšãã®é
åãæšæºç»é²
CompositeResolver.RegisterAndSetAsDefault(
UnsafeDirectBlitResolver.Instance,
MessagePack.Unity.Extension.UnityBlitResolver.Instance
);
// --- ããšã¯éä¿¡ã§äœ¿ããšãèªç¶ã«âã®ãã©ãŒãããã§éåä¿¡ãã
await client.SendAsync(new CustomStruct { Hp = 99 });
ããã¯äžåã®åŠçãå
¥ããªãããã転éåŠçã«ãããŠçè«äžæéãšãããŸãããã ãstructã¯ã³ããŒãçºçããããã巚倧ãªstructãå®çŸ©ãããªãå
šãŠrefã§åŠçãããªã©ã®å·¥å€«ãšèŠçŽãããªããšãéã«é
ããªãå¯èœæ§ãããã®ã§ããã®ç¹ã¯æ³šæã
倧éã®TransformãéããäŸãã°Vector3ã®é
åãªã©ã§ã¯ãåãããããæå¹ã«æŽ»çšã§ããããšãšæããŸãã
gRPCã®Bidirectional Streamingã§ã¯ãã¡ãªçç±
çŽ ã®gRPCã§ã Bidirectional Streaming ãšããŠåæ¹åéä¿¡ã®æ©èœãæã£ãŠããŸããããããMagicOnionã®ã¹ããªãŒãã³ã°RPC㯠Bidirectional Streamingã®äžã«æ§ç¯ãããŠããŸãã
// protoã«ããBidirectional Streamingå®çŸ©
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
ããããå€ãã®çç±ã§Bidirectional Streamingããªã¢ã«ã¿ã€ã éä¿¡ã®RPCãšããŠäœ¿ãããšã¯é£ããã§ããããæå€§ã®ç¹ã¯ããããããã®ç¶æ
ã ãšRPCã§ã¯ãªããããã³ãã¯ã·ã§ã³ãç¹ãã£ãåŸãoneof(ã²ãšã€ã®åãè€æ°ã®åãæã€)ã§å®çŸ©ããRequest/Responseãåå²ãããŠãåŒã³åºãããã¡ãœããã«æã§æ¯ãåããããšã«ãªãã§ãããããããŸã§ã¯ããããšããŠãããŸã ãŸã è¶³ããªããã®ã¯å€ã
- ã¯ã©ã€ã¢ã³ãããµãŒããŒåŽã®å®è¡å®äºãåŸ
ãŠãªãïŒãªã¯ãšã¹ããéä¿¡ã§ãããšãããšããã§å¶åŸ¡ãæ»ãïŒ
- åŸ
ãŠãªããšããããšã§ãªã¯ãšã¹ãã«å¯Ÿããæ»ãå€ãäŸå€ãåãåããªã
- è€æ°ã®æ¥ç¶ãæããææ®µãçšæãããŠããªã
ããããåŠçããããã®ä»çµã¿ãäœã£ãŠããªããçµå±protoã®çæããBidirectional Streamingã®ãã³ãã¬ãŒãããã¯éããããªããããå®è£
ããŽãã£ã€ããŠããŸãã®ã¯é¿ããããªãã§ããããMagicOnionã®StreamingHubã¯ãã³ãã¯ã·ã§ã³ã®ç¢ºç«ãBidirectional Streamingã®äžã«æ§ç¯ãã€ã€ãããã®éä¿¡ãã¬ãŒã ã®äžã§ç¬èªã®è»œéãªãããã³ã«ãæµãããšã«ãã£ãŠãC#ãšããŠèªç¶ã«æ±ãããªã¢ã«ã¿ã€ã éä¿¡çšã®RPCãå®çŸããŠããŸãã
忣æŠç¥ãšgRPCãéžã¶çç±
Unityã®ããã®ä»ã®ãªã¢ã«ã¿ã€ã éä¿¡ãšã³ãžã³ãªã©ãšéããMagicOnionèªäœã¯ããŒããã©ã³ãµãŒãæã¡ãŸããã忣åŠçã®ããã®æŠç¥ã¯å¹Ÿã€ããããŸãããã¯ã©ãŠããã©ãããã©ãŒã ãåçš®ããã«ãŠã§ã¢ã掻çšããæ¹åã®éžæããèŠãããŠããŸããäŸãã°ãå®å
šã«ç¬ç«ããŠã€ã³ã¡ã¢ãªã§ãã¹ãã£ã³ã°ããå ŽåããµãŒããŒã®å°ã®éžæã¯å€éšã®Service Discoveryã«ä»»ããŠããŸãã®ãäžã€ã
![]()
ããäžã€ã¯ãTCPããŒããã©ã³ãµãŒãçšããŠå®å
šã«åæ£ãã€ã€ãGroupã«ãããããŒããã£ã¹ãåŠçãRedisã«å§è²ããããšã§ç°ãªãå°ã«ç¹ãã£ãã¯ã©ã€ã¢ã³ããžã®ããŒã¿ã®éä¿¡ãå¯èœã«ããŸããããã¯æšæºã§MagicOnion.RedisãšããŠæäŸããŠããŸããäŸãã°ãã£ããã®å®è£
ãªã©ã«åããŠããã§ãããã
![]()
ä»ãMagicOnionèªäœã¯gRPCãã®ãã®ãšåãããã«ãããããMicroservicesã®å®è£
ã«ãé©ããŠããããããµãŒããŒéã§ã³ãã¯ã·ã§ã³ãç¹ãã§ããµãŒããŒ-ãµãŒããŒRPCã«ãã£ãŠæ§æãçµãããšãå¯èœã§ãã
ããŠãMagicOnionã¯gRPCã®äžã«æ§ç¯ãããŠããŸãããåæã«æå€§ã®ç¹åŸŽã§ãã.protoã«ããèšèªéäŸåã®RPCæäŸãšããéšåãå®å
šã«ç¡èŠããŠããŸããããŸãã«ãããã¯ãŒã¯éä¿¡ãHTTP/2(TCP)ã«éå®ããããšããã®ã¯ãå¿
ãããã²ãŒã åããšã¯èšãé£ããããããŸãããããã§ããªããgRPCãéžã¶ã¹ããšèããçç±ããããŸãã
äžã€ã¯ã©ã€ãã©ãªãšããŠã®æç床ã§ãããããUnityå«ãããµãŒããŒ/ã¯ã©ã€ã¢ã³ãå®è£
ã䜿ããéä¿¡ã©ã€ãã©ãªã¯ååšããªãäžã«ãã³ã¢éšå(å
šèšèªå
±éã®gRPC C)ã¯googleå«ããŠå§åçã«äžã®äžã§äœ¿ãããŠãããããå®å®æ§ãéåžžã«é«ããã²ãŒã ã«ç¹åããéšåã ãã®éä¿¡ã®ç¬èªå®è£
ã¯ãã§ããªãã¯ãªãã§ãããããäžããå®å®æ§ãé«ããŠããããšãèãããšãä¹ãããã®ã«ã¯ä¹ã£ããã»ããç¡é£ã§ãããã
ãã ããäž»ã«ããã©ãŒãã³ã¹é¢ã§gRPCã®C#ãã€ã³ãã£ã³ã°ã«é¢ããŠã¯äžæºããããŸãããã®ããgRPC C Coreã¯ãã®ãŸãŸäœ¿ãã€ã€ãC#ãã€ã³ãã£ã³ã°ã ããå®å
šã«çœ®ãæããŠããŸãã®ã¯ã¢ãªã ãšæã£ãŠããŸããå°ãªããšãUnityåŽïŒã¯ã©ã€ã¢ã³ãéä¿¡ïŒã®ã¿ãªãã°ãçŸå®çãã€å¹æãé«ããšæã£ãŠããã®ã§ã
ããäžã€ã¯ãšã³ã·ã¹ãã ã§ãgRPCã¯çŸè¡äžä»£ã®RPCãšããŠã»ãŒããã¡ã¯ãã¹ã¿ã³ããŒããšèšããå°äœã確ç«ãããããå€ãã®ãµãŒããŒçšããã«ãŠã§ã¢ã察å¿ããŠããŸããNginxã®gRPC察å¿ãEnvoyã«ãããªã¯ãšã¹ãåäœã®ããŒããã©ã³ã·ã³ã°ãªã©ãHTTP/2, gRPCãšããæ¥çã¹ã¿ã³ããŒããªãããã³ã«ã ããããæãããæ©æµãå€ããããŸãããŸããgRPCã¯è±èªã§ãæ¥æ¬èªã§ããããã°ãäºäŸã¹ã©ã€ããè±å¯ãªãããããè¯ãã·ã¹ãã æ§ç¯ãç®æããããã®ã倧ããªå©ç¹ã§ãã
MagicOnionãã¢ããªã±ãŒã·ã§ã³ã¬ã€ã€ãŒã§ã¯ç¬èªã®ãã®ãæ§ç¯ãããŠããŸãããã€ã³ãã©çãªæå³ã§ã¯gRPCãã®ãã®ãªã®ã§ãããã«ãŠã§ã¢ããå
±æãããŠãããã¬ããžããã»ãšãã©ããã®ãŸãŸé©çšã§ããŸãã
çŸä»£ã®ãµãŒããŒã¢ãŒããã¯ãã£ã¯ã¯ã©ãŠãåæã§ããã¹ãã ãšæããŸãããã¯ã©ãŠããããã€ããŒã®æäŸããã€ã³ãã©ãããã«ãŠã§ã¢ã«ãã«ã«ä¹ã£ãã£ãã·ã¹ãã ã®ã»ãããå
šéšèªåã§æãšããšããã·ã¹ãã ãããåªããçµæãæ®ãããšæã£ãŠããŸãããã£ãŠãç¹ã«ã€ã³ãã©ã«é¢ãããã¬ãŒã ã¯ãŒã¯èªäœã®æ©èœã¯èãããŠããã¹ãã§ãããã
APIéä¿¡ç³»ãžã®å¯Ÿå¿
MagicOnionã¯Unified Network EngineãæšæŠããŠããŸãããã®å Žåã®Unifiedã¯ããµãŒããŒãšã¯ã©ã€ã¢ã³ããC#ã§çµ±äžãããããšããæå³ããã®ã§ã¯ãªããŠããªã¢ã«ã¿ã€ã éä¿¡ç³»ãšAPIéä¿¡ç³»ãçµ±äžãããŠæ±ããããšãæå³ããŠããŸããAPIéä¿¡ç³»ãåãããã«ãã€ã³ã¿ãŒãã§ã€ã¹ãå
±æããC#ãšããŠèªç¶ã«ã¡ãœãããå®çŸ©ããã°ãã¯ã©ã€ã¢ã³ãã³ãŒããèªåã§çæãããä»çµã¿ã«ãªã£ãŠããŸãã
// StreamingHubãšåãããã«ã€ã³ã¿ãŒãã§ã€ã¹ãå®çŸ©ããå
±æãã
public interface IMyFirstService : IService<IMyFirstService>
{
UnaryResult<int> SumAsync(int x, int y);
}
// ãµãŒããŒåŽã¯ãã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ãã
public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService
{
// ããžã«ã«ãã¯ãããžã«ããæ»ãå€ãTask<T>ãããªããŠãè¯ã
public async UnaryResult<int> SumAsync(int x, int y)
{
// å
éšã¯å®å
šã«éåæå¯Ÿå¿ãªã®ã§èªç¶ãªæžãå³ã§ãã³ããããã³ã°APIãå®è£
ã§ãã
Logger.Debug($"Received:{x}, {y}");
return x + y;
}
}
// ã¯ã©ã€ã¢ã³ãåŽã§ã¯ãã®ã€ã³ã¿ãŒãã§ã€ã¹ãåãåºããåŒã³åºã
// ã€ã³ã¿ãŒãã§ã€ã¹ãéä¿¡ã³ãŒãå®è£
ã«çœ®ãæãããããã·ãååŸãã
var client = MagicOnionClient.Create<IMyFirstService>(channel);
// èªç¶ãªæãã«åŒæ°ãæž¡ããèªç¶ãªæãã«æ»ãå€ãåãåãã
var result = await client.SumAsync(100, 200);
APIç³»ã«ãããŠããã¬ãŒã ã¯ãŒã¯ã¬ãã«ã§å
šãŠãéåæã»ãã³ããããã³ã°ã§ããããšã培åºãããŠããŸãããC#ã®async/awaitèšèªæ©èœã«ããã»ãšãã©èªç¶ã«èŠããããšã«æåããŠããŸãããªã¯ãšã¹ãååŸãããã¯ãããã£ã«ã¿ãŒæ©èœãçšæãããŠããŠããããèªç¶ã«éåæã§åŠçãããããã«ãªã£ãŠããŸãã
// é©çšãããã¡ãœããã«[SampleFilter]ã®ããã«å±æ§ãä»äžãããšéãåããããã
public class SampleFilterAttribute : MagicOnionFilterAttribute
{
// ååŸã®åŠçã®ããã¯ãéåæã§ããã€C#ã®èšèªä»æ§ã®ãŸãŸèªç¶ã«æžããããã«ãªã£ãŠããŸã
// ãã®ãã£ã«ã¿ãéãåãããæ§ãããŸããç¶ã§ããããšããMagicOnionã®ç±æ¥ãšãªã£ãŠããŸã
public override async ValueTask Invoke(ServiceContext context)
{
try
{
/* ååŠç */
await Next(context); // ã¡ãœããæ¬äœããããã¯æ¬¡ã®ãã£ã«ã¿ãŒãåŒã¶
/* åŸåŠç */
}
catch
{
/* äŸå€æåŠç */
throw;
}
finally
{
/* åŸå§æ« */
}
}
}
ãªãããã£ã«ã¿ãŒã¯StreamingHubã§ãåæ§ã«äœ¿ããŸãã
Swagger
APIã¯åäœç¢ºèªãã¥ãããåžžã«Unityããå©ãã®ãé¢åãããããgRPCã§ã¯Postmanã®ãããªããŒã«ã§å©ãããšãã§ããªãããšãããã§MagicOnionã§ã¯Swaggerã«ããå®è¡å¯èœãªAPIããã¥ã¡ã³ããèªåçæããããšã«ããŸãããMagicOnionèªèº«ãHTTP/1ãµãŒããŒãšãªããã¹ãã£ã³ã°ãããããå¥éå€éšã®ãããã·ãµãŒããŒãç«ãŠãå¿
èŠãªã©ã¯ãããŸãããèµ·åéšåã®ã³ãŒãã«æ°è¡å ããã ãã§ãã
![]()
ããã§ç°¡åã«åäœç¢ºèªã§ããã»ãããããã°ã³ãã³ããªã©ãAPIãšããŠå®çŸ©ããã ãSwaggeräžã«äžŠã¶ããããããã°çšã«ããŒã¿ããŒã¹ãæäœããã³ãã³ããªã©ãç°¡åã«çšæããŠããããšãå¯èœãããããŸããã
çŸç¶StreamingHubã¯å¯Ÿå¿ããŠããŸããããWebSocketãšMagicOnionãæ¥ç¶ããWebSocketGatewayãäœãäºå®ã¯ãããŸãã
ãããã€ãšãã¹ãã£ã³ã°
åŸæ¥ãC#ãµãŒããŒãµã€ãæå€§ã®é£é¢ã¯ãããã€ã©ããããããã¹ãã£ã³ã°ã©ããããããšããããšã§ããããªã«ãWindows Serverã§ããããgRPCã ã£ãããæŽã«ãªãIISãããªãããäœèšã«é£ããã£ãããããããçŸåšã¯ç°¡åã§ããDockerã§ã³ã³ããåããŠããŸãã°ãå¥ã«C#ã ããã£ãŠç¹å¥ãªããšã¯äœäžã€ãããŸããã.NET Coreã«ãã£ãŠäœæãããMagicOnionã¢ããªã±ãŒã·ã§ã³ãã³ã³ããåããã®ã¯ç¹ã«è€éãªããšããªãç°¡åã§(宿
ã¯ãã ã®.NET Coreã³ã³ãœãŒã«ã¢ããªã±ãŒã·ã§ã³ã§ã)ãããšã¯ãããLinuxã³ã³ããã«ãããã€ããŠããŸãã°è¯ããECSã§ãFargateã§ãGKEã§ãAKSã§ããã©ãã§ãè¯ãã®ã§ãããã®ããã®ãã©ã¯ãã£ã¹ãããããã«ååšããæ§ã
ãªèšäºã®ãã®ããã®ãŸãŸé©çšããã°è¯ãã§ãããã
çŸç¶C#ã§ã®ã³ã³ããåã®æçŸ©ã¯ãããŒã«ã«ç°å¢ãæ§ç¯ãããããšããããããéçºç°å¢/æ¬çªç°å¢ã«ç°¡åã«éã¶ãããç¹ã«ä»ãŸã§C#/Windowsã«éŠŽæã¿ã®ãªãã£ã人ãç¹å¥ãªããšãåŠã¶ããšãªãè±å¯ãªã€ã³ãã©ç¥èããã®ãŸãŸæŽ»ãããŠé©çšããããšãã§ããããšããããšãæã倧ããã®ã§ã¯ãªãããªãããšæããŠããŸãã
çµããã«
Cy#ã®ç念ã¯ãC#ã®å¯èœæ§ãåãéããŠãããã§ããMagicOnionã¯ãªã¢ã«ã¿ã€ã éä¿¡ç³»ã®ããã ãã«å°å
¥ããŠããã£ãŠãããã§ãããAPIéä¿¡ç³»ã ãã§ãåå以äžã«æ©èœããŸããé«éãªéä¿¡ãšãå§çž®ãèæ
®ãããã·ãªã¢ã©ã€ã¶ãé©çšããããããå°å
¥ããã°éä¿¡é¢é£ã«é¢ããŠäžåæ©ãå¿
èŠã¯ãªããªãã§ãããããŸããUnityã«ãããŠã¯async/awaitãæŽ»çšãããŠãããããææ°ã®C#ãå°å
¥ããŠããããã®ã²ãŒããŠã§ã€ãšããŠãæ©èœãããããããŸããã
ãªã¢ã«ã¿ã€ã éä¿¡ãã¬ãŒã ã¯ãŒã¯ãšããŠã¯ãClient-Serverã®RPCããæäŸããŠããŸãããããéã«ãããããã°ä»ã¯å
šéšãªããšããªããŸãïŒã¢ãã«ãããŸããå®è£
éãããã»ã©å€ãã¯ãªãã¯ãïŒãäœèšãªãã®ãã€ããŠãªããŠãRPCã«é¢ããŠæé«ã®æè§ŠãïŒãšããã©ãŒãã³ã¹ããšèšãããã§ããgRPC C#ãã€ã³ãã£ã³ã°ã®ã»ãã§æ°ã«ãªããšãããããã®ã§æé«ã®ããã©ãŒãã³ã¹ãšããã®ã¯ããŸã次ã«åã£ãŠãããŸãïŒãæäŸã§ããã®ã§ã¯ãªãããšèªè² ããŠããŸãã
ãŸããå®å
šã«èªåã®ã·ã¹ãã ã§å®åãããããVR/ARã®å±ç€ºãªã©éå®ãããLANç°å¢ãªã©ã§ããLANå
ã§ãµãŒããŒãèµ·åããŠããã ãã§åé¡ãªãå±éããããšãã§ããŸãâŠâŠïŒ
Cy#ã§ã¯MagicOnionãäžå¿ã«ãC#掻çšã®éãäœãåºããŠãããããšèããŠããŸãã
æ¯éãçããã詊ããŠããã ããã°å¹žãã§ãã
äœããããŸãããGitHubã®issueãç§ã®å人å®ãŠã«ã©ããã