install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/76-webgl-ws-polling-bridge" ~/.claude/skills/majiayu000-claude-skill-registry-76-webgl-ws-polling-bridge && rm -rf "$T"
manifest:
skills/data/76-webgl-ws-polling-bridge/SKILL.mdsource content
76-webgl-ws-polling-bridge
Status: ACTIVE AppliesTo: v10 Type: Policy / Contract
Purpose
WebGL에서 WebSocket 이벤트를 폴링(Polling) 기반으로 전달하는
.jslib ↔ C# 간 계약을 정의한다.
이 문서는 인터페이스 계약이 핵심이며, 구현 코드가 정답이다.
Hard Rules
-
SendMessage/콜백 기반 설계 금지
- WebGL에서 WebSocket 이벤트를 C#으로 전달할 때
콜백을 사용하지 않는다.SendMessage - 모든 이벤트는
로만 소비한다.WS_PollEvent
- WebGL에서 WebSocket 이벤트를 C#으로 전달할 때
-
이벤트 소비는 Tick()에서만 수행
호출 시에만 이벤트 큐를 drain한다.Tick()- 메인 스레드 보장.
-
이벤트 처리량 제한 필수
- 한
에서 최대 N개의 이벤트만 처리한다.Tick() - 무한 루프/폭주 방지.
- 한
Function Contract (C# → JS)
아래 함수들은
.jslib에서 구현하고, C#에서 [DllImport("__Internal")]로 호출한다.
WS_Connect
[DllImport("__Internal")] private static extern int WS_Connect(string url, string subProtocolsJson);
- 설명: WebSocket 연결 시작
- 파라미터:
: WebSocket URL (wss:// 필수, WebGL에서 ws:// 금지)url
: sub-protocol 배열의 JSON 문자열 (nullable, 예:subProtocolsJson
)["devian.v1"]
- 리턴:
(0 이상), 실패 시 음수socketId
WS_GetState
[DllImport("__Internal")] private static extern int WS_GetState(int socketId);
- 설명: WebSocket 연결 상태 조회
- 리턴:
,0=CONNECTING
,1=OPEN
,2=CLOSING
,3=CLOSED-1=INVALID
WS_SendBinary
[DllImport("__Internal")] private static extern int WS_SendBinary(int socketId, IntPtr ptr, int len);
- 설명: 바이너리 메시지 전송
- 파라미터:
: WASM heap 상의 버퍼 포인터 (ptr
)GCHandle.Alloc(Pinned)
: 버퍼 길이len
- 리턴:
, 음수=실패0=성공
WS_Close
[DllImport("__Internal")] private static extern void WS_Close(int socketId, int code, string reason);
- 설명: WebSocket 종료 요청
- 파라미터:
: 종료 코드 (예: 1000)code
: 종료 사유 (nullable)reason
WS_PollEvent
[DllImport("__Internal")] private static extern int WS_PollEvent( int socketId, out int eventType, out int code, out IntPtr dataPtr, out int dataLen, out IntPtr messagePtr);
- 설명: 이벤트 큐에서 다음 이벤트를 가져온다
- 리턴:
= 이벤트 없음0
= 이벤트 있음 (출력값 유효)1
- 출력 파라미터:
: 이벤트 타입 (아래 참조)eventType
: CLOSE 이벤트의 종료 코드, ERROR/MESSAGE에서는 0code
: MESSAGE 이벤트의 바이너리 데이터 포인터 (_malloc 할당)dataPtr
: MESSAGE 이벤트의 데이터 길이dataLen
: ERROR/CLOSE 이벤트의 UTF8 문자열 포인터 (_malloc 할당, nullable)messagePtr
WS_FreeBuffer
[DllImport("__Internal")] private static extern void WS_FreeBuffer(IntPtr ptr);
- 설명: MESSAGE 이벤트의
해제dataPtr - 필수: MESSAGE 수신 후 반드시 호출
WS_FreeString
[DllImport("__Internal")] private static extern void WS_FreeString(IntPtr ptr);
- 설명: ERROR/CLOSE 이벤트의
해제messagePtr - 필수: messagePtr이 non-null이면 반드시 호출
EventType Enum
| 값 | 이름 | 설명 |
|---|---|---|
| 0 | NONE | 이벤트 없음 (리턴값 0과 동일) |
| 1 | OPEN | 연결 성공 |
| 2 | CLOSE | 연결 종료 (code, messagePtr 유효) |
| 3 | ERROR | 오류 발생 (messagePtr 유효) |
| 4 | MESSAGE | 바이너리 메시지 수신 (dataPtr, dataLen 유효) |
WS_PollEvent 사용 패턴
// Tick() 내부 const int MaxEventsPerTick = 64; for (int i = 0; i < MaxEventsPerTick; i++) { int result = WS_PollEvent(_socketId, out int eventType, out int code, out IntPtr dataPtr, out int dataLen, out IntPtr messagePtr); if (result == 0) break; // 이벤트 없음 switch (eventType) { case 1: // OPEN EnqueueDispatch(() => OnOpen?.Invoke()); break; case 2: // CLOSE string reason = messagePtr != IntPtr.Zero ? Marshal.PtrToStringUTF8(messagePtr) : ""; EnqueueDispatch(() => OnClose?.Invoke((ushort)code, reason)); if (messagePtr != IntPtr.Zero) WS_FreeString(messagePtr); break; case 3: // ERROR string errorMsg = messagePtr != IntPtr.Zero ? Marshal.PtrToStringUTF8(messagePtr) : "Unknown error"; EnqueueDispatch(() => OnError?.Invoke(new Exception(errorMsg))); if (messagePtr != IntPtr.Zero) WS_FreeString(messagePtr); break; case 4: // MESSAGE // ArrayPool로 복사 후 core.OnFrame 호출 var buffer = ArrayPool<byte>.Shared.Rent(dataLen); Marshal.Copy(dataPtr, buffer, 0, dataLen); _core.OnFrame(_sessionId, buffer.AsSpan(0, dataLen)); ArrayPool<byte>.Shared.Return(buffer); WS_FreeBuffer(dataPtr); break; } }
DoD (Hard Gate)
-
가 유일한 이벤트 소비 경로이다 (SendMessage 없음)WS_PollEvent - MESSAGE 수신 후
호출됨WS_FreeBuffer(dataPtr) - ERROR/CLOSE 수신 후 messagePtr이 non-null이면
호출됨WS_FreeString(messagePtr) - 한 Tick에서 처리하는 이벤트 개수에 상한이 있음
Reference
- 메모리 규칙: 77-webgl-jslib-memory-rules
- WebSocket 클라이언트: 72-network-ws-client
- Transport Adapter: 70-ws-transport-adapter