Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
ygopro2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
hex
ygopro2
Commits
d323f7a9
Commit
d323f7a9
authored
Feb 07, 2026
by
hex
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
optimize tcp weak-network handling and ocgcore performance baseline
parent
0f33a907
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1123 additions
and
396 deletions
+1123
-396
Assets/SibylSystem/MonoHelpers/TcpHelper.cs
Assets/SibylSystem/MonoHelpers/TcpHelper.cs
+672
-189
Assets/SibylSystem/MyCard/MyCard.cs
Assets/SibylSystem/MyCard/MyCard.cs
+2
-2
Assets/SibylSystem/Ocgcore/Ocgcore.cs
Assets/SibylSystem/Ocgcore/Ocgcore.cs
+356
-200
Assets/SibylSystem/Program.cs
Assets/SibylSystem/Program.cs
+1
-1
Assets/SibylSystem/selectServer/SelectServer.cs
Assets/SibylSystem/selectServer/SelectServer.cs
+1
-4
docs/ocgcore-performance-and-stability-notes-2026-02-07.md
docs/ocgcore-performance-and-stability-notes-2026-02-07.md
+91
-0
No files found.
Assets/SibylSystem/MonoHelpers/TcpHelper.cs
View file @
d323f7a9
using
System
;
using
System.Collections.Concurrent
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Net.Sockets
;
...
...
@@ -14,9 +15,148 @@ public static class TcpHelper
static
NetworkStream
networkStream
=
null
;
static
bool
canjoin
=
true
;
const
int
SioKeepAliveVals
=
-
1744830460
;
static
readonly
object
stateLock
=
new
object
();
static
ConnectionState
state
=
null
;
static
readonly
ConcurrentQueue
<
byte
[
]>
injectedIncoming
=
new
ConcurrentQueue
<
byte
[
]>
();
static
long
injectedIncomingBytes
=
0
;
static
int
injectedIncomingPackets
=
0
;
static
int
joinInProgress
=
0
;
static
int
generationCounter
=
0
;
static
int
disconnectGeneration
=
0
;
static
Thread
receiverThread
=
null
;
static
Thread
senderThread
=
null
;
public
static
int
ConnectTimeoutMs
=
10000
;
public
static
int
KeepAliveTimeMs
=
20000
;
public
static
int
KeepAliveIntervalMs
=
5000
;
public
static
int
DuelIdleHeartbeatMs
=
15000
;
public
static
int
SendRetryDelayMs
=
50
;
public
static
int
MaxTransientSendRetry
=
3
;
public
static
int
SendTimeoutMs
=
9999
*
1000
;
public
static
int
ReceiveTimeoutMs
=
9999
*
1000
;
public
static
bool
TcpNoDelay
=
true
;
public
static
bool
TcpKeepAlive
=
true
;
public
static
int
OutgoingQueueLimitBytes
=
4
*
1024
*
1024
;
public
static
int
OutgoingQueueLimitPackets
=
4096
;
public
static
int
IncomingQueueLimitBytes
=
8
*
1024
*
1024
;
public
static
int
IncomingQueueLimitPackets
=
8192
;
static
bool
roomListChecking
=
false
;
sealed
class
ConnectionState
:
IDisposable
{
public
readonly
int
Generation
;
public
readonly
TcpClient
Client
;
public
readonly
NetworkStream
Stream
;
public
readonly
Socket
Socket
;
public
readonly
ConcurrentQueue
<
byte
[
]>
Incoming
=
new
ConcurrentQueue
<
byte
[
]>
();
public
readonly
ConcurrentQueue
<
byte
[
]>
Outgoing
=
new
ConcurrentQueue
<
byte
[
]>
();
public
readonly
AutoResetEvent
OutgoingSignal
=
new
AutoResetEvent
(
false
);
public
readonly
CancellationTokenSource
Cts
=
new
CancellationTokenSource
();
public
volatile
bool
Closing
=
false
;
public
int
DisconnectRequested
=
0
;
public
long
IncomingBytes
=
0
;
public
int
IncomingPackets
=
0
;
public
long
OutgoingBytes
=
0
;
public
int
OutgoingPackets
=
0
;
public
int
LastReceiveTick
=
0
;
public
int
LastSendTick
=
0
;
public
int
LastHeartbeatTick
=
0
;
public
ConnectionState
(
int
generation
,
TcpClient
client
)
{
Generation
=
generation
;
Client
=
client
;
Stream
=
client
.
GetStream
();
Socket
=
client
.
Client
;
int
now
=
Environment
.
TickCount
;
LastReceiveTick
=
now
;
LastSendTick
=
now
;
LastHeartbeatTick
=
now
;
}
public
void
Dispose
()
{
try
{
OutgoingSignal
.
Dispose
();
}
catch
{
}
try
{
Cts
.
Dispose
();
}
catch
{
}
}
}
public
static
void
Disconnect
(
bool
userInitiated
=
true
)
{
onDisConnected
=
false
;
Interlocked
.
Exchange
(
ref
disconnectGeneration
,
0
);
CloseActiveConnection
();
}
static
int
TickNow
()
{
return
Environment
.
TickCount
;
}
static
bool
IsElapsed
(
int
fromTick
,
int
durationMs
)
{
return
unchecked
(
TickNow
()
-
fromTick
)
>=
durationMs
;
}
static
bool
IsTransientSocketError
(
SocketError
socketError
)
{
return
socketError
==
SocketError
.
WouldBlock
||
socketError
==
SocketError
.
IOPending
||
socketError
==
SocketError
.
NoBufferSpaceAvailable
||
socketError
==
SocketError
.
TimedOut
||
socketError
==
SocketError
.
Interrupted
||
socketError
==
SocketError
.
InProgress
||
socketError
==
SocketError
.
TryAgain
;
}
static
void
TryDuelIdleHeartbeat
(
ConnectionState
localState
)
{
if
(
localState
==
null
||
localState
.
Closing
)
return
;
Program
program
=
Program
.
I
();
if
(
program
==
null
||
program
.
ocgcore
==
null
)
return
;
if
(
program
.
ocgcore
.
condition
!=
Ocgcore
.
Condition
.
duel
)
return
;
int
lastReceive
=
Volatile
.
Read
(
ref
localState
.
LastReceiveTick
);
int
lastSend
=
Volatile
.
Read
(
ref
localState
.
LastSendTick
);
int
lastActivity
=
unchecked
(
lastSend
-
lastReceive
)
>
0
?
lastSend
:
lastReceive
;
if
(!
IsElapsed
(
lastActivity
,
DuelIdleHeartbeatMs
))
return
;
int
lastHeartbeat
=
Volatile
.
Read
(
ref
localState
.
LastHeartbeatTick
);
if
(!
IsElapsed
(
lastHeartbeat
,
DuelIdleHeartbeatMs
))
return
;
Volatile
.
Write
(
ref
localState
.
LastHeartbeatTick
,
TickNow
());
CtosMessage_TimeConfirm
();
}
public
static
void
join
(
string
ipString
,
string
name
,
...
...
@@ -25,209 +165,427 @@ public static class TcpHelper
string
version
)
{
if
(
canjoin
)
if
(
Interlocked
.
CompareExchange
(
ref
joinInProgress
,
1
,
0
)
!=
0
)
{
return
;
}
TcpClient
client
=
null
;
ConnectionState
newState
=
null
;
try
{
if
(
tcpClient
==
null
||
tcpClient
.
Connected
==
false
)
onDisConnected
=
false
;
roomListChecking
=
pswString
==
"L"
;
CloseActiveConnection
();
int
port
=
int
.
Parse
(
portString
);
client
=
new
TcpClientWithTimeout
(
ipString
,
port
,
ConnectTimeoutMs
).
Connect
();
ConfigureSocket
(
client
);
int
generation
=
Interlocked
.
Increment
(
ref
generationCounter
);
newState
=
new
ConnectionState
(
generation
,
client
);
try
{
canjoin
=
false
;
try
{
tcpClient
=
new
TcpClientWithTimeout
(
ipString
,
int
.
Parse
(
portString
),
3000
).
Connect
();
networkStream
=
tcpClient
.
GetStream
();
Thread
t
=
new
Thread
(
receiver
);
t
.
Start
();
CtosMessage_ExternalAddress
(
ipString
);
CtosMessage_PlayerInfo
(
name
);
if
(
pswString
==
"L"
)
{
roomListChecking
=
true
;
}
else
{
roomListChecking
=
false
;
}
CtosMessage_JoinGame
(
pswString
,
version
);
}
catch
(
Exception
e
)
{
Program
.
DEBUGLOG
(
"onDisConnected 10"
);
}
canjoin
=
true
;
newState
.
Stream
.
ReadTimeout
=
ReceiveTimeoutMs
;
}
catch
{
}
try
{
newState
.
Stream
.
WriteTimeout
=
SendTimeoutMs
;
}
catch
{
}
lock
(
stateLock
)
{
state
=
newState
;
tcpClient
=
client
;
networkStream
=
newState
.
Stream
;
}
receiverThread
=
new
Thread
(
ReceiverLoop
);
receiverThread
.
IsBackground
=
true
;
receiverThread
.
Start
(
newState
);
senderThread
=
new
Thread
(
SenderLoop
);
senderThread
.
IsBackground
=
true
;
senderThread
.
Start
(
newState
);
CtosMessage_ExternalAddress
(
ipString
);
CtosMessage_PlayerInfo
(
name
);
CtosMessage_JoinGame
(
pswString
,
version
);
}
else
catch
(
Exception
e
)
{
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
"onDisConnected 1"
);
Program
.
DEBUGLOG
(
"onDisConnected 10: "
+
e
.
Message
);
try
{
client
?.
Close
();
}
catch
{
}
try
{
newState
?.
Dispose
();
}
catch
{
}
CloseActiveConnection
();
}
finally
{
Interlocked
.
Exchange
(
ref
joinInProgress
,
0
);
}
}
public
static
void
receiver
(
)
static
void
ConfigureSocket
(
TcpClient
client
)
{
if
(
client
==
null
)
return
;
try
{
client
.
NoDelay
=
TcpNoDelay
;
}
catch
{
}
try
{
client
.
Client
.
NoDelay
=
TcpNoDelay
;
}
catch
{
}
try
{
while
(
tcpClient
!=
null
&&
networkStream
!=
null
&&
tcpClient
.
Connected
&&
Program
.
Running
)
client
.
Client
.
SetSocketOption
(
SocketOptionLevel
.
Socket
,
SocketOptionName
.
KeepAlive
,
TcpKeepAlive
);
}
catch
{
}
try
{
byte
[]
keepAlive
=
new
byte
[
12
];
BitConverter
.
GetBytes
((
uint
)
1
).
CopyTo
(
keepAlive
,
0
);
BitConverter
.
GetBytes
((
uint
)
KeepAliveTimeMs
).
CopyTo
(
keepAlive
,
4
);
BitConverter
.
GetBytes
((
uint
)
KeepAliveIntervalMs
).
CopyTo
(
keepAlive
,
8
);
client
.
Client
.
IOControl
((
IOControlCode
)
SioKeepAliveVals
,
keepAlive
,
null
);
}
catch
{
}
try
{
client
.
Client
.
SendTimeout
=
SendTimeoutMs
;
client
.
Client
.
ReceiveTimeout
=
ReceiveTimeoutMs
;
}
catch
{
}
}
static
void
ReceiverLoop
(
object
obj
)
{
var
localState
=
(
ConnectionState
)
obj
;
try
{
var
token
=
localState
.
Cts
.
Token
;
while
(!
token
.
IsCancellationRequested
&&
Program
.
Running
)
{
byte
[]
data
=
SocketMaster
.
ReadPacket
(
networkStream
);
addDateJumoLine
(
data
);
byte
[]
data
=
SocketMaster
.
ReadPacket
(
localState
.
Stream
,
token
);
if
(
data
==
null
)
{
RequestDisconnect
(
localState
,
"onDisConnected 2"
);
break
;
}
Volatile
.
Write
(
ref
localState
.
LastReceiveTick
,
TickNow
());
if
(!
TryEnqueueIncoming
(
localState
,
data
))
{
break
;
}
}
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
"onDisConnected 2"
);
}
catch
(
Exception
e
)
{
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
"onDisConnected 3"
);
RequestDisconnect
(
localState
,
"onDisConnected 3: "
+
e
.
Message
);
}
}
static
bool
TryEnqueueIncoming
(
ConnectionState
localState
,
byte
[]
data
)
{
if
(
data
==
null
)
return
false
;
long
bytes
=
Interlocked
.
Add
(
ref
localState
.
IncomingBytes
,
data
.
Length
);
int
packets
=
Interlocked
.
Increment
(
ref
localState
.
IncomingPackets
);
if
(
bytes
>
IncomingQueueLimitBytes
||
packets
>
IncomingQueueLimitPackets
)
{
RequestDisconnect
(
localState
,
"onDisConnected incoming overflow"
);
return
false
;
}
localState
.
Incoming
.
Enqueue
(
data
);
return
true
;
}
// For offline duel core / replay engine: inject packets into the same main-thread dispatcher.
public
static
void
addDateJumoLine
(
byte
[]
data
)
{
Monitor
.
Enter
(
datas
);
if
(
data
==
null
)
return
;
long
bytes
=
Interlocked
.
Add
(
ref
injectedIncomingBytes
,
data
.
Length
);
int
packets
=
Interlocked
.
Increment
(
ref
injectedIncomingPackets
);
if
(
bytes
>
IncomingQueueLimitBytes
||
packets
>
IncomingQueueLimitPackets
)
{
Interlocked
.
Add
(
ref
injectedIncomingBytes
,
-
data
.
Length
);
Interlocked
.
Decrement
(
ref
injectedIncomingPackets
);
return
;
}
injectedIncoming
.
Enqueue
(
data
);
}
static
void
RequestDisconnect
(
ConnectionState
localState
,
string
debugLog
)
{
if
(
localState
==
null
)
return
;
if
(
Interlocked
.
Exchange
(
ref
localState
.
DisconnectRequested
,
1
)
!=
0
)
return
;
localState
.
Closing
=
true
;
try
{
datas
.
Add
(
data
);
localState
.
Cts
.
Cancel
(
);
}
catch
(
System
.
Exception
e
)
catch
{
}
try
{
// Debug.Log(e
);
localState
.
OutgoingSignal
.
Set
(
);
}
Monitor
.
Exit
(
datas
);
}
catch
{
}
public
static
bool
onDisConnected
=
false
;
bool
shouldNotifyMainThread
=
false
;
lock
(
stateLock
)
{
shouldNotifyMainThread
=
ReferenceEquals
(
state
,
localState
);
}
static
List
<
byte
[
]>
datas
=
new
List
<
byte
[
]>
();
if
(
shouldNotifyMainThread
)
{
Interlocked
.
Exchange
(
ref
disconnectGeneration
,
localState
.
Generation
);
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
debugLog
);
}
}
public
static
void
preFrameFun
ction
()
static
void
CloseActiveConne
ction
()
{
if
(
datas
.
Count
>
0
)
ConnectionState
oldState
=
null
;
lock
(
stateLock
)
{
if
(
Monitor
.
TryEnter
(
datas
))
oldState
=
state
;
state
=
null
;
}
if
(
oldState
!=
null
)
{
oldState
.
Closing
=
true
;
try
{
for
(
int
i
=
0
;
i
<
datas
.
Count
;
i
++)
oldState
.
Cts
.
Cancel
();
}
catch
{
}
try
{
oldState
.
OutgoingSignal
.
Set
();
}
catch
{
}
try
{
if
(
oldState
.
Client
!=
null
)
{
try
{
MemoryStream
memoryStream
=
new
MemoryStream
(
datas
[
i
]);
BinaryReader
r
=
new
BinaryReader
(
memoryStream
);
var
ms
=
(
StocMessage
)(
r
.
ReadByte
());
switch
(
ms
)
if
(
oldState
.
Client
.
Connected
)
{
case
StocMessage
.
GameMsg
:
Program
.
I
().
room
.
StocMessage_GameMsg
(
r
);
break
;
case
StocMessage
.
ErrorMsg
:
Program
.
I
().
room
.
StocMessage_ErrorMsg
(
r
);
break
;
case
StocMessage
.
SelectHand
:
Program
.
I
().
room
.
StocMessage_SelectHand
(
r
);
break
;
case
StocMessage
.
SelectTp
:
Program
.
I
().
room
.
StocMessage_SelectTp
(
r
);
break
;
case
StocMessage
.
HandResult
:
Program
.
I
().
room
.
StocMessage_HandResult
(
r
);
break
;
case
StocMessage
.
TpResult
:
Program
.
I
().
room
.
StocMessage_TpResult
(
r
);
break
;
case
StocMessage
.
ChangeSide
:
Program
.
I
().
room
.
StocMessage_ChangeSide
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
WaitingSide
:
Program
.
I
().
room
.
StocMessage_WaitingSide
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
DeckCount
:
Program
.
I
().
room
.
StocMessage_DeckCount
(
r
);
break
;
case
StocMessage
.
CreateGame
:
Program
.
I
().
room
.
StocMessage_CreateGame
(
r
);
break
;
case
StocMessage
.
JoinGame
:
Program
.
I
().
room
.
StocMessage_JoinGame
(
r
);
break
;
case
StocMessage
.
TypeChange
:
Program
.
I
().
room
.
StocMessage_TypeChange
(
r
);
break
;
case
StocMessage
.
LeaveGame
:
Program
.
I
().
room
.
StocMessage_LeaveGame
(
r
);
break
;
case
StocMessage
.
DuelStart
:
Program
.
I
().
room
.
StocMessage_DuelStart
(
r
);
break
;
case
StocMessage
.
DuelEnd
:
Program
.
I
().
room
.
StocMessage_DuelEnd
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
Replay
:
Program
.
I
().
room
.
StocMessage_Replay
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
TimeLimit
:
Program
.
I
().
ocgcore
.
StocMessage_TimeLimit
(
r
);
break
;
case
StocMessage
.
Chat
:
Program
.
I
().
room
.
StocMessage_Chat
(
r
);
break
;
case
StocMessage
.
HsPlayerEnter
:
Program
.
I
().
room
.
StocMessage_HsPlayerEnter
(
r
);
break
;
case
StocMessage
.
HsPlayerChange
:
Program
.
I
().
room
.
StocMessage_HsPlayerChange
(
r
);
break
;
case
StocMessage
.
HsWatchChange
:
Program
.
I
().
room
.
StocMessage_HsWatchChange
(
r
);
break
;
case
StocMessage
.
TeammateSurrender
:
Program
.
I
().
room
.
StocMessage_TeammateSurrender
(
r
);
break
;
case
YGOSharp
.
Network
.
Enums
.
StocMessage
.
RoomList
:
((
Room
)
Program
.
I
().
room
).
StocMessage_RoomList
(
r
);
break
;
default
:
break
;
oldState
.
Socket
.
Shutdown
(
SocketShutdown
.
Both
);
}
}
catch
(
System
.
Exception
e
)
catch
{
}
try
{
// Program.DEBUGLOG(e
);
oldState
.
Client
.
Close
(
);
}
catch
{
}
}
datas
.
Clear
();
Monitor
.
Exit
(
datas
);
}
catch
{
}
try
{
oldState
.
Stream
.
Close
();
}
catch
{
}
oldState
.
Dispose
();
}
if
(
onDisConnected
==
true
)
tcpClient
=
null
;
networkStream
=
null
;
}
public
static
volatile
bool
onDisConnected
=
false
;
static
void
DispatchIncomingPacket
(
byte
[]
packet
)
{
try
{
onDisConnected
=
false
;
Program
.
I
().
ocgcore
.
setDefaultReturnServant
();
MemoryStream
memoryStream
=
new
MemoryStream
(
packet
);
BinaryReader
r
=
new
BinaryReader
(
memoryStream
);
var
ms
=
(
StocMessage
)(
r
.
ReadByte
());
switch
(
ms
)
{
case
StocMessage
.
GameMsg
:
Program
.
I
().
room
.
StocMessage_GameMsg
(
r
);
break
;
case
StocMessage
.
ErrorMsg
:
Program
.
I
().
room
.
StocMessage_ErrorMsg
(
r
);
break
;
case
StocMessage
.
SelectHand
:
Program
.
I
().
room
.
StocMessage_SelectHand
(
r
);
break
;
case
StocMessage
.
SelectTp
:
Program
.
I
().
room
.
StocMessage_SelectTp
(
r
);
break
;
case
StocMessage
.
HandResult
:
Program
.
I
().
room
.
StocMessage_HandResult
(
r
);
break
;
case
StocMessage
.
TpResult
:
Program
.
I
().
room
.
StocMessage_TpResult
(
r
);
break
;
case
StocMessage
.
ChangeSide
:
Program
.
I
().
room
.
StocMessage_ChangeSide
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
WaitingSide
:
Program
.
I
().
room
.
StocMessage_WaitingSide
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
DeckCount
:
Program
.
I
().
room
.
StocMessage_DeckCount
(
r
);
break
;
case
StocMessage
.
CreateGame
:
Program
.
I
().
room
.
StocMessage_CreateGame
(
r
);
break
;
case
StocMessage
.
JoinGame
:
Program
.
I
().
room
.
StocMessage_JoinGame
(
r
);
break
;
case
StocMessage
.
TypeChange
:
Program
.
I
().
room
.
StocMessage_TypeChange
(
r
);
break
;
case
StocMessage
.
LeaveGame
:
Program
.
I
().
room
.
StocMessage_LeaveGame
(
r
);
break
;
case
StocMessage
.
DuelStart
:
Program
.
I
().
room
.
StocMessage_DuelStart
(
r
);
break
;
case
StocMessage
.
DuelEnd
:
Program
.
I
().
room
.
StocMessage_DuelEnd
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
Replay
:
Program
.
I
().
room
.
StocMessage_Replay
(
r
);
// TcpHelper.SaveRecord();
break
;
case
StocMessage
.
TimeLimit
:
Program
.
I
().
ocgcore
.
StocMessage_TimeLimit
(
r
);
break
;
case
StocMessage
.
Chat
:
Program
.
I
().
room
.
StocMessage_Chat
(
r
);
break
;
case
StocMessage
.
HsPlayerEnter
:
Program
.
I
().
room
.
StocMessage_HsPlayerEnter
(
r
);
break
;
case
StocMessage
.
HsPlayerChange
:
Program
.
I
().
room
.
StocMessage_HsPlayerChange
(
r
);
break
;
case
StocMessage
.
HsWatchChange
:
Program
.
I
().
room
.
StocMessage_HsWatchChange
(
r
);
break
;
case
StocMessage
.
TeammateSurrender
:
Program
.
I
().
room
.
StocMessage_TeammateSurrender
(
r
);
break
;
case
YGOSharp
.
Network
.
Enums
.
StocMessage
.
RoomList
:
((
Room
)
Program
.
I
().
room
).
StocMessage_RoomList
(
r
);
break
;
default
:
break
;
}
}
catch
(
System
.
Exception
e
)
{
// Program.DEBUGLOG(e);
}
}
try
public
static
void
preFrameFunction
()
{
ConnectionState
localState
=
null
;
lock
(
stateLock
)
{
localState
=
state
;
}
TryDuelIdleHeartbeat
(
localState
);
if
(
localState
!=
null
&&
!
ReferenceEquals
(
tcpClient
,
localState
.
Client
))
{
RequestDisconnect
(
localState
,
"onDisConnected external close"
);
}
while
(
injectedIncoming
.
TryDequeue
(
out
var
injectedPacket
))
{
Interlocked
.
Add
(
ref
injectedIncomingBytes
,
-
injectedPacket
.
Length
);
Interlocked
.
Decrement
(
ref
injectedIncomingPackets
);
DispatchIncomingPacket
(
injectedPacket
);
}
if
(
localState
!=
null
)
{
while
(
localState
.
Incoming
.
TryDequeue
(
out
var
packet
))
{
if
(
tcpClient
!=
null
)
{
if
(
tcpClient
.
Connected
)
{
tcpClient
.
Client
.
Shutdown
(
SocketShutdown
.
Both
);
}
tcpClient
.
Close
();
}
Interlocked
.
Add
(
ref
localState
.
IncomingBytes
,
-
packet
.
Length
);
Interlocked
.
Decrement
(
ref
localState
.
IncomingPackets
);
DispatchIncomingPacket
(
packet
);
}
catch
(
Exception
e
)
}
if
(
onDisConnected
==
true
)
{
onDisConnected
=
false
;
int
gen
=
Interlocked
.
Exchange
(
ref
disconnectGeneration
,
0
);
bool
closeNow
=
false
;
lock
(
stateLock
)
{
// Debug.LogWarning("Socket cleanup failed: " + e.Message)
;
closeNow
=
gen
!=
0
&&
state
!=
null
&&
state
.
Generation
==
gen
;
}
if
(
gen
!=
0
&&
!
closeNow
)
return
;
// stale disconnect from previous connection, ignore
Program
.
I
().
ocgcore
.
setDefaultReturnServant
();
if
(
closeNow
)
CloseActiveConnection
();
tcpClient
=
null
;
if
(
Program
.
I
().
ocgcore
.
isShowed
==
false
)
{
if
(
Program
.
I
().
menu
.
isShowed
==
false
)
...
...
@@ -260,47 +618,130 @@ public static class TcpHelper
public
static
void
Send
(
Package
message
)
{
if
(
tcpClient
!=
null
&&
tcpClient
.
Connected
)
ConnectionState
localState
=
null
;
lock
(
stateLock
)
{
// 用线程池代替 Thread
System
.
Threading
.
ThreadPool
.
QueueUserWorkItem
(
sender
,
message
);
localState
=
state
;
}
}
static
object
locker
=
new
object
();
if
(
localState
==
null
||
localState
.
Closing
)
return
;
static
void
sender
(
object
state
)
{
try
{
Package
message
=
(
Package
)
state
;
byte
[]
data
=
message
.
Data
.
get
();
byte
[]
frame
=
BuildFrame
(
message
);
if
(
frame
==
null
)
return
;
long
bytes
=
Interlocked
.
Add
(
ref
localState
.
OutgoingBytes
,
frame
.
Length
);
int
packets
=
Interlocked
.
Increment
(
ref
localState
.
OutgoingPackets
);
if
(
bytes
>
OutgoingQueueLimitBytes
||
packets
>
OutgoingQueueLimitPackets
)
{
Interlocked
.
Add
(
ref
localState
.
OutgoingBytes
,
-
frame
.
Length
);
Interlocked
.
Decrement
(
ref
localState
.
OutgoingPackets
);
RequestDisconnect
(
localState
,
"onDisConnected outgoing overflow"
);
return
;
}
localState
.
Outgoing
.
Enqueue
(
frame
);
localState
.
OutgoingSignal
.
Set
();
}
catch
(
Exception
e
)
{
RequestDisconnect
(
localState
,
"onDisConnected 5: "
+
e
.
Message
);
}
}
// 预分配足够的 buffer,避免多余的内存流
int
totalLen
=
2
+
1
+
data
.
Length
;
byte
[]
s
=
new
byte
[
totalLen
];
static
byte
[]
BuildFrame
(
Package
message
)
{
if
(
message
==
null
||
message
.
Data
==
null
)
return
null
;
byte
[]
data
=
message
.
Data
.
get
();
// 写入长度(short,包含功能码长度)
short
len
=
(
short
)(
data
.
Length
+
1
);
s
[
0
]
=
(
byte
)(
len
&
0xFF
);
s
[
1
]
=
(
byte
)((
len
>>
8
)
&
0xFF
);
int
totalLen
=
2
+
1
+
data
.
Length
;
byte
[]
s
=
new
byte
[
totalLen
];
// 写入功能码
s
[
2
]
=
(
byte
)
message
.
Fuction
;
ushort
len
=
(
ushort
)(
data
.
Length
+
1
);
s
[
0
]
=
(
byte
)(
len
&
0xFF
);
s
[
1
]
=
(
byte
)((
len
>>
8
)
&
0xFF
);
s
[
2
]
=
(
byte
)
message
.
Fuction
;
Buffer
.
BlockCopy
(
data
,
0
,
s
,
3
,
data
.
Length
);
return
s
;
}
// 写入数据
Buffer
.
BlockCopy
(
data
,
0
,
s
,
3
,
data
.
Length
);
static
void
SenderLoop
(
object
obj
)
{
var
localState
=
(
ConnectionState
)
obj
;
var
token
=
localState
.
Cts
.
Token
;
// 只锁发送
lock
(
locker
)
try
{
while
(!
token
.
IsCancellationRequested
&&
Program
.
Running
)
{
tcpClient
.
Client
.
Send
(
s
);
if
(!
localState
.
Outgoing
.
TryDequeue
(
out
var
frame
))
{
localState
.
OutgoingSignal
.
WaitOne
(
100
);
continue
;
}
Interlocked
.
Add
(
ref
localState
.
OutgoingBytes
,
-
frame
.
Length
);
Interlocked
.
Decrement
(
ref
localState
.
OutgoingPackets
);
try
{
SendAll
(
localState
.
Socket
,
frame
,
token
);
Volatile
.
Write
(
ref
localState
.
LastSendTick
,
TickNow
());
}
catch
(
Exception
e
)
{
RequestDisconnect
(
localState
,
"onDisConnected 5: "
+
e
.
Message
);
break
;
}
}
}
catch
(
Exception
e
)
{
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
"onDisConnected 5: "
+
e
.
Message
+
"\n"
+
e
.
StackTrace
);
RequestDisconnect
(
localState
,
"onDisConnected sender loop: "
+
e
.
Message
);
}
}
static
void
SendAll
(
Socket
socket
,
byte
[]
buffer
,
CancellationToken
token
)
{
if
(
socket
==
null
||
buffer
==
null
)
return
;
int
offset
=
0
;
int
retry
=
0
;
while
(
offset
<
buffer
.
Length
)
{
if
(
token
.
IsCancellationRequested
)
return
;
try
{
int
sent
=
socket
.
Send
(
buffer
,
offset
,
buffer
.
Length
-
offset
,
SocketFlags
.
None
);
if
(
sent
<=
0
)
throw
new
IOException
(
"socket send returned 0"
);
offset
+=
sent
;
retry
=
0
;
}
catch
(
SocketException
socketException
)
{
if
(
IsTransientSocketError
(
socketException
.
SocketErrorCode
)
&&
retry
<
MaxTransientSendRetry
)
{
retry
++;
Thread
.
Sleep
(
SendRetryDelayMs
*
retry
);
continue
;
}
throw
;
}
}
}
...
...
@@ -870,30 +1311,72 @@ public static class BinaryExtensions
public
class
SocketMaster
{
static
byte
[]
ReadFull
(
NetworkStream
stream
,
int
length
)
const
int
HeaderLength
=
2
;
const
int
MaxPayloadLength
=
0xFFFF
;
static
byte
[]
ReadFull
(
NetworkStream
stream
,
int
length
,
CancellationToken
token
)
{
if
(
stream
==
null
)
return
null
;
if
(
length
==
0
)
return
Array
.
Empty
<
byte
>();
if
(
length
<
0
)
throw
new
ArgumentOutOfRangeException
(
nameof
(
length
));
var
buf
=
new
byte
[
length
];
int
rlen
=
0
;
while
(
rlen
<
buf
.
Length
)
{
int
currentLength
=
stream
.
Read
(
buf
,
rlen
,
buf
.
Length
-
rlen
);
rlen
+=
currentLength
;
if
(
currentLength
==
0
)
if
(
token
.
IsCancellationRequested
)
return
null
;
int
currentLength
=
0
;
try
{
currentLength
=
stream
.
Read
(
buf
,
rlen
,
buf
.
Length
-
rlen
);
}
catch
(
IOException
ioEx
)
when
(
IsTimeout
(
ioEx
))
{
continue
;
}
catch
(
SocketException
se
)
when
(
se
.
SocketErrorCode
==
SocketError
.
TimedOut
)
{
TcpHelper
.
onDisConnected
=
true
;
Program
.
DEBUGLOG
(
"onDisConnected 6"
);
break
;
continue
;
}
catch
(
ObjectDisposedException
)
{
return
null
;
}
rlen
+=
currentLength
;
if
(
currentLength
==
0
)
return
null
;
}
return
buf
;
}
public
static
byte
[]
ReadPacket
(
NetworkStream
stream
)
static
bool
IsTimeout
(
IOException
ioEx
)
{
var
hdr
=
ReadFull
(
stream
,
2
);
if
(
ioEx
==
null
)
return
false
;
if
(
ioEx
.
InnerException
is
SocketException
se
)
{
return
se
.
SocketErrorCode
==
SocketError
.
TimedOut
;
}
return
false
;
}
public
static
byte
[]
ReadPacket
(
NetworkStream
stream
,
CancellationToken
token
)
{
var
hdr
=
ReadFull
(
stream
,
HeaderLength
,
token
);
if
(
hdr
==
null
)
return
null
;
var
plen
=
BitConverter
.
ToUInt16
(
hdr
,
0
);
var
buf
=
ReadFull
(
stream
,
plen
);
if
(
plen
==
0
||
plen
>
MaxPayloadLength
)
return
null
;
var
buf
=
ReadFull
(
stream
,
plen
,
token
);
return
buf
;
}
}
...
...
@@ -1009,4 +1492,4 @@ public class TcpClientWithTimeout
exception
=
ex
;
}
}
}
\ No newline at end of file
}
Assets/SibylSystem/MyCard/MyCard.cs
View file @
d323f7a9
...
...
@@ -64,9 +64,9 @@ public class MyCard : WindowServantSP
{
TerminateRequest
();
}
if
(
TcpHelper
.
tcpClient
!=
null
&&
TcpHelper
.
tcpClient
.
Connected
)
if
(
TcpHelper
.
tcpClient
!=
null
)
{
TcpHelper
.
tcpClient
.
Close
(
);
TcpHelper
.
Disconnect
(
true
);
}
}
...
...
Assets/SibylSystem/Ocgcore/Ocgcore.cs
View file @
d323f7a9
...
...
@@ -48,6 +48,20 @@ public class Ocgcore : ServantWithCardDescription
List
<
linkMask
>
linkMaskList
=
new
List
<
linkMask
>();
readonly
List
<
gameCard
>
realizeToClearCards
=
new
List
<
gameCard
>(
64
);
readonly
List
<
gameCard
>
realizeOpponentMonsterCards
=
new
List
<
gameCard
>(
16
);
readonly
List
<
gameCard
>
realizeOpponentSpellCards
=
new
List
<
gameCard
>(
16
);
readonly
List
<
GPS
>
realizeLinkPositions
=
new
List
<
GPS
>(
32
);
readonly
List
<
linkMask
>
realizeLinkMaskRemoveBuffer
=
new
List
<
linkMask
>(
16
);
readonly
List
<
gameCard
>
realizeOpponentHandLine
=
new
List
<
gameCard
>(
16
);
readonly
List
<
thunder_locator
>
realizeThunderRemoveBuffer
=
new
List
<
thunder_locator
>(
16
);
readonly
List
<
gameCard
>
realizeMyPendulumCards
=
new
List
<
gameCard
>(
4
);
readonly
List
<
gameCard
>
realizeOpponentPendulumCards
=
new
List
<
gameCard
>(
4
);
readonly
List
<
gameCard
>
realizeEmptyOverlayList
=
new
List
<
gameCard
>(
0
);
readonly
Dictionary
<
int
,
List
<
gameCard
>>
realizeOverlayMap
=
new
Dictionary
<
int
,
List
<
gameCard
>>(
64
);
readonly
Stack
<
List
<
gameCard
>>
realizeOverlayListPool
=
new
Stack
<
List
<
gameCard
>>();
linkMask
makeLinkMask
(
GPS
p
)
{
linkMask
ma
=
new
linkMask
();
...
...
@@ -100,6 +114,106 @@ public class Ocgcore : ServantWithCardDescription
}
}
static
int
OverlayKey
(
GPS
p
)
{
return
((
int
)
p
.
controller
<<
24
)
|
((
int
)
p
.
location
<<
16
)
|
((
int
)
p
.
sequence
<<
8
);
}
static
int
OverlayKeyFromCard
(
gameCard
card
)
{
return
((
int
)
card
.
p
.
controller
<<
24
)
|
((
int
)(
card
.
p
.
location
|
(
UInt32
)
CardLocation
.
Overlay
)
<<
16
)
|
((
int
)
card
.
p
.
sequence
<<
8
);
}
void
ClearOverlayMapCache
()
{
foreach
(
var
pair
in
realizeOverlayMap
)
{
pair
.
Value
.
Clear
();
realizeOverlayListPool
.
Push
(
pair
.
Value
);
}
realizeOverlayMap
.
Clear
();
}
void
BuildOverlayMapCache
()
{
ClearOverlayMapCache
();
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
{
gameCard
card
=
cards
[
i
];
if
(
card
.
gameObject
.
activeInHierarchy
==
false
)
{
continue
;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Overlay
)
==
0
)
{
continue
;
}
int
key
=
OverlayKey
(
card
.
p
);
List
<
gameCard
>
list
;
if
(
realizeOverlayMap
.
TryGetValue
(
key
,
out
list
)
==
false
)
{
if
(
realizeOverlayListPool
.
Count
>
0
)
{
list
=
realizeOverlayListPool
.
Pop
();
}
else
{
list
=
new
List
<
gameCard
>(
4
);
}
realizeOverlayMap
.
Add
(
key
,
list
);
}
list
.
Add
(
card
);
}
}
List
<
gameCard
>
GetOverlayElementsFromCache
(
gameCard
card
)
{
if
(
card
==
null
)
{
return
null
;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Overlay
)
>
0
)
{
return
null
;
}
List
<
gameCard
>
list
;
realizeOverlayMap
.
TryGetValue
(
OverlayKeyFromCard
(
card
),
out
list
);
return
list
;
}
void
SetLocationCount
(
TMPro
.
TextMeshPro
textmesh
,
int
count
,
int
faceUpCount
,
CardLocation
location
)
{
if
(
count
<
2
)
{
textmesh
.
text
=
""
;
return
;
}
if
(
location
==
CardLocation
.
Extra
)
{
textmesh
.
text
=
count
.
ToString
()
+
"("
+
faceUpCount
.
ToString
()
+
")"
;
}
else
{
textmesh
.
text
=
count
.
ToString
();
}
}
gameCardCondition
get_point_worldcondition
(
GPS
p
)
{
gameCardCondition
return_value
=
gameCardCondition
.
floating_clickable
;
...
...
@@ -826,10 +940,8 @@ public class Ocgcore : ServantWithCardDescription
if
(
TcpHelper
.
tcpClient
.
Connected
)
{
setDefaultReturnServant
();
TcpHelper
.
tcpClient
.
Client
.
Shutdown
(
0
);
TcpHelper
.
tcpClient
.
Close
();
}
TcpHelper
.
tcpClient
=
null
;
TcpHelper
.
Disconnect
(
true
)
;
}
returnTo
();
}
...
...
@@ -838,13 +950,7 @@ public class Ocgcore : ServantWithCardDescription
{
if
(
TcpHelper
.
tcpClient
!=
null
)
{
/*if (TcpHelper.tcpClient.Connected)
{
setDefaultReturnServant();
TcpHelper.tcpClient.Client.Shutdown(0);
TcpHelper.tcpClient.Close();
} */
TcpHelper
.
tcpClient
=
null
;
TcpHelper
.
Disconnect
(
true
);
}
returnTo
();
}
...
...
@@ -7307,30 +7413,190 @@ public class Ocgcore : ServantWithCardDescription
{
someCardIsShowed
=
false
;
float
real
=
(
Program
.
fieldSize
-
1
)
*
0.9f
+
1f
;
realizeToClearCards
.
Clear
();
realizeOpponentMonsterCards
.
Clear
();
realizeOpponentSpellCards
.
Clear
();
realizeLinkPositions
.
Clear
();
realizeLinkMaskRemoveBuffer
.
Clear
();
realizeOpponentHandLine
.
Clear
();
realizeThunderRemoveBuffer
.
Clear
();
realizeMyPendulumCards
.
Clear
();
realizeOpponentPendulumCards
.
Clear
();
int
myDeckCount
=
0
;
int
myExtraCount
=
0
;
int
myExtraFaceUpCount
=
0
;
int
myGraveCount
=
0
;
int
myRemovedCount
=
0
;
int
opDeckCount
=
0
;
int
opExtraCount
=
0
;
int
opExtraFaceUpCount
=
0
;
int
opGraveCount
=
0
;
int
opRemovedCount
=
0
;
int
myFieldCode
=
0
;
int
opFieldCode
=
0
;
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
cards
[
i
].
cookie_cared
=
false
;
cards
[
i
].
p_line_off
();
cards
[
i
].
sortButtons
();
cards
[
i
].
opMonsterWithBackGroundCard
=
false
;
cards
[
i
].
isMinBlockMode
=
false
;
cards
[
i
].
overFatherCount
=
0
;
gameCard
card
=
cards
[
i
];
card
.
cookie_cared
=
false
;
card
.
p_line_off
();
card
.
sortButtons
();
card
.
opMonsterWithBackGroundCard
=
false
;
card
.
isMinBlockMode
=
false
;
card
.
overFatherCount
=
0
;
if
(
card
.
p
.
location
==
(
uint
)
CardLocation
.
Unknown
)
{
realizeToClearCards
.
Add
(
card
);
}
if
(
card
.
p
.
location
==
(
uint
)
CardLocation
.
Search
)
{
card
.
isShowed
=
true
;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Overlay
)
==
0
)
{
if
(
card
.
p
.
controller
==
1
)
{
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
MonsterZone
)
>
0
)
{
realizeOpponentMonsterCards
.
Add
(
card
);
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
{
realizeOpponentSpellCards
.
Add
(
card
);
}
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
{
if
(
Program
.
I
().
setting
.
setting
.
Vfield
.
value
&&
card
.
p
.
sequence
==
5
&&
(
card
.
p
.
position
&
(
Int32
)
CardPosition
.
FaceUp
)
>
0
)
{
if
(
card
.
p
.
controller
==
0
)
{
myFieldCode
=
card
.
get_data
().
Id
;
}
else
{
opFieldCode
=
card
.
get_data
().
Id
;
}
}
}
}
if
(
card
.
p
.
controller
==
0
)
{
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Deck
)
>
0
)
{
myDeckCount
++;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Extra
)
>
0
)
{
myExtraCount
++;
if
((
card
.
p
.
position
&
(
UInt32
)
CardPosition
.
FaceUp
)
>
0
)
{
myExtraFaceUpCount
++;
}
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Grave
)
>
0
)
{
myGraveCount
++;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Removed
)
>
0
)
{
myRemovedCount
++;
}
}
else
{
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Deck
)
>
0
)
{
opDeckCount
++;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Extra
)
>
0
)
{
opExtraCount
++;
if
((
card
.
p
.
position
&
(
UInt32
)
CardPosition
.
FaceUp
)
>
0
)
{
opExtraFaceUpCount
++;
}
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Grave
)
>
0
)
{
opGraveCount
++;
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Removed
)
>
0
)
{
opRemovedCount
++;
}
}
}
List
<
gameCard
>
to_clear
=
new
List
<
gameCard
>();
for
(
int
i
=
0
;
i
<
realizeToClearCards
.
Count
;
i
++)
{
realizeToClearCards
[
i
].
hide
();
}
bool
usePendulumDisplay
=
Program
.
I
().
setting
.
setting
.
Vpedium
.
value
;
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
gameCard
card
=
cards
[
i
];
if
(
card
.
gameObject
.
activeInHierarchy
==
false
||
card
.
cookie_cared
)
{
if
(
cards
[
i
].
p
.
location
==
(
uint
)
CardLocation
.
Unknown
)
continue
;
}
if
(
(
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
Hand
)
>
0
&&
card
.
p
.
controller
==
1
)
{
realizeOpponentHandLine
.
Add
(
card
);
}
if
((
card
.
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
==
0
)
{
continue
;
}
if
(
usePendulumDisplay
)
{
if
((
card
.
p
.
sequence
==
0
||
card
.
p
.
sequence
==
4
)
==
false
)
{
to_clear
.
Add
(
cards
[
i
]);
continue
;
}
if
((
card
.
get_data
().
Type
&
(
int
)
CardType
.
Pendulum
)
==
0
)
{
continue
;
}
}
else
{
if
(
card
.
p
.
sequence
!=
6
&&
card
.
p
.
sequence
!=
7
)
{
continue
;
}
}
for
(
int
i
=
0
;
i
<
to_clear
.
Count
;
i
++)
{
to_clear
[
i
].
hide
();
if
(
card
.
p
.
controller
==
0
)
{
realizeMyPendulumCards
.
Add
(
card
);
}
else
{
realizeOpponentPendulumCards
.
Add
(
card
);
}
}
//for (int i = 0; i < cards.Count; i++) if (cards[i].gameObject.activeInHierarchy)
...
...
@@ -7387,16 +7653,6 @@ public class Ocgcore : ServantWithCardDescription
}
}
}
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
if
(
cards
[
i
].
p
.
location
==
(
uint
)
CardLocation
.
Search
)
{
cards
[
i
].
isShowed
=
true
;
}
}
List
<
List
<
gameCard
>>
lines
=
new
List
<
List
<
gameCard
>>();
UInt32
preController
=
9999
;
UInt32
preLocation
=
9999
;
...
...
@@ -7482,39 +7738,20 @@ public class Ocgcore : ServantWithCardDescription
gameField
.
isLong
=
false
;
List
<
gameCard
>
op_m
=
new
List
<
gameCard
>();
List
<
gameCard
>
op_s
=
new
List
<
gameCard
>();
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
if
(
cards
[
i
].
p
.
controller
==
1
)
{
if
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
Overlay
)
==
0
)
{
if
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
MonsterZone
)
>
0
)
{
op_m
.
Add
(
cards
[
i
]);
}
if
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
{
op_s
.
Add
(
cards
[
i
]);
}
}
}
}
for
(
int
m
=
0
;
m
<
op_m
.
Count
;
m
++)
for
(
int
m
=
0
;
m
<
realizeOpponentMonsterCards
.
Count
;
m
++)
{
if
((
op_m
[
m
].
p
.
position
&
(
UInt32
)
CardPosition
.
FaceUp
)
>
0
)
if
((
realizeOpponentMonsterCards
[
m
].
p
.
position
&
(
UInt32
)
CardPosition
.
FaceUp
)
>
0
)
{
for
(
int
s
=
0
;
s
<
op_
s
.
Count
;
s
++)
for
(
int
s
=
0
;
s
<
realizeOpponentSpellCard
s
.
Count
;
s
++)
{
if
(
op_m
[
m
].
p
.
sequence
==
op_s
[
s
].
p
.
sequence
)
if
(
realizeOpponentMonsterCards
[
m
].
p
.
sequence
==
realizeOpponentSpellCards
[
s
].
p
.
sequence
)
{
if
(
op_m
[
m
].
p
.
sequence
<
5
)
if
(
realizeOpponentMonsterCards
[
m
].
p
.
sequence
<
5
)
{
op_m
[
m
].
opMonsterWithBackGroundCard
=
true
;
realizeOpponentMonsterCards
[
m
].
opMonsterWithBackGroundCard
=
true
;
//op_m[m].isMinBlockMode = true;
if
(
Program
.
getVerticalTransparency
()
>=
0.5f
)
{
...
...
@@ -7650,7 +7887,7 @@ public class Ocgcore : ServantWithCardDescription
}
}
List
<
GPS
>
linkPs
=
new
List
<
GPS
>()
;
List
<
GPS
>
linkPs
=
realizeLinkPositions
;
for
(
int
curHang
=
2
;
curHang
<=
4
;
curHang
++)
{
...
...
@@ -7815,7 +8052,7 @@ public class Ocgcore : ServantWithCardDescription
}
}
List
<
linkMask
>
removeList
=
new
List
<
linkMask
>()
;
List
<
linkMask
>
removeList
=
realizeLinkMaskRemoveBuffer
;
for
(
int
i
=
0
;
i
<
linkMaskList
.
Count
;
i
++)
{
...
...
@@ -7843,7 +8080,6 @@ public class Ocgcore : ServantWithCardDescription
}
removeList
.
Clear
();
removeList
=
null
;
for
(
int
i
=
0
;
i
<
linkMaskList
.
Count
;
i
++)
{
...
...
@@ -7852,19 +8088,7 @@ public class Ocgcore : ServantWithCardDescription
gameField
.
Update
();
//op hand
List
<
gameCard
>
line
=
new
List
<
gameCard
>();
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
if
(
cards
[
i
].
cookie_cared
==
false
)
{
if
(
(
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
Hand
)
>
0
&&
cards
[
i
].
p
.
controller
==
1
)
{
line
.
Add
(
cards
[
i
]);
}
}
List
<
gameCard
>
line
=
realizeOpponentHandLine
;
for
(
int
index
=
0
;
index
<
line
.
Count
;
index
++)
{
Vector3
want_position
=
Vector3
.
zero
;
...
...
@@ -7898,10 +8122,13 @@ public class Ocgcore : ServantWithCardDescription
gameField
.
thunders
[
i
].
needDestroy
=
true
;
}
BuildOverlayMapCache
();
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
List
<
gameCard
>
overlayed_cards
=
GCS_cardGetOverlayElements
(
cards
[
i
]);
List
<
gameCard
>
overlayed_cards
=
GetOverlayElementsFromCache
(
cards
[
i
]);
int
overlayCount
=
overlayed_cards
!=
null
?
overlayed_cards
.
Count
:
0
;
int
overC
=
0
;
if
(
Program
.
getVerticalTransparency
()
>
0.5f
)
{
...
...
@@ -7910,17 +8137,20 @@ public class Ocgcore : ServantWithCardDescription
&&
(
cards
[
i
].
p
.
location
&
(
Int32
)
CardLocation
.
Onfield
)
>
0
)
{
overC
=
overlay
ed_cards
.
Count
;
overC
=
overlayCount
;
}
}
cards
[
i
].
set_overlay_light
(
overC
);
cards
[
i
].
set_overlay_see_button
(
overlay
ed_cards
.
Count
>
0
);
for
(
int
x
=
0
;
x
<
overlayed_cards
.
Count
;
x
++
)
cards
[
i
].
set_overlay_see_button
(
overlayCount
>
0
);
if
(
overlayCount
>
0
)
{
overlayed_cards
[
x
].
overFatherCount
=
overlayed_cards
.
Count
;
if
(
overlayed_cards
[
x
].
isShowed
)
for
(
int
x
=
0
;
x
<
overlayCount
;
x
++)
{
animation_thunder
(
overlayed_cards
[
x
].
gameObject
,
cards
[
i
].
gameObject
);
overlayed_cards
[
x
].
overFatherCount
=
overlayCount
;
if
(
overlayed_cards
[
x
].
isShowed
)
{
animation_thunder
(
overlayed_cards
[
x
].
gameObject
,
cards
[
i
].
gameObject
);
}
}
}
foreach
(
var
item
in
cards
[
i
].
target
)
...
...
@@ -7935,7 +8165,7 @@ public class Ocgcore : ServantWithCardDescription
}
}
List
<
thunder_locator
>
needRemoveThunder
=
new
List
<
thunder_locator
>()
;
List
<
thunder_locator
>
needRemoveThunder
=
realizeThunderRemoveBuffer
;
for
(
int
i
=
0
;
i
<
gameField
.
thunders
.
Count
;
i
++)
{
if
(
gameField
.
thunders
[
i
].
needDestroy
==
true
)
...
...
@@ -7950,36 +8180,15 @@ public class Ocgcore : ServantWithCardDescription
}
needRemoveThunder
.
Clear
();
ClearOverlayMapCache
();
//p effect
gameField
.
relocatePnums
(
Program
.
I
().
setting
.
setting
.
Vpedium
.
value
);
if
(
Program
.
I
().
setting
.
setting
.
Vpedium
.
value
==
true
)
gameField
.
relocatePnums
(
usePendulumDisplay
);
if
(
usePendulumDisplay
==
true
)
{
List
<
gameCard
>
my_p_cards
=
new
List
<
gameCard
>()
;
List
<
gameCard
>
my_p_cards
=
realizeMyPendulumCards
;
List
<
gameCard
>
op_p_cards
=
new
List
<
gameCard
>();
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
if
(
cards
[
i
].
cookie_cared
==
false
)
{
if
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
{
if
(
cards
[
i
].
p
.
sequence
==
0
||
cards
[
i
].
p
.
sequence
==
4
)
{
if
((
cards
[
i
].
get_data
().
Type
&
(
int
)
CardType
.
Pendulum
)
>
0
)
{
if
(
cards
[
i
].
p
.
controller
==
0
)
{
my_p_cards
.
Add
(
cards
[
i
]);
}
else
{
op_p_cards
.
Add
(
cards
[
i
]);
}
}
}
}
}
List
<
gameCard
>
op_p_cards
=
realizeOpponentPendulumCards
;
if
(
MasterRule
>=
4
)
{
...
...
@@ -8108,29 +8317,9 @@ public class Ocgcore : ServantWithCardDescription
{
//p effect pain
List
<
gameCard
>
my_p_cards
=
new
List
<
gameCard
>();
List
<
gameCard
>
op_p_cards
=
new
List
<
gameCard
>();
List
<
gameCard
>
my_p_cards
=
realizeMyPendulumCards
;
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
if
(
cards
[
i
].
cookie_cared
==
false
)
{
if
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
{
if
(
cards
[
i
].
p
.
sequence
==
6
||
cards
[
i
].
p
.
sequence
==
7
)
{
if
(
cards
[
i
].
p
.
controller
==
0
)
{
my_p_cards
.
Add
(
cards
[
i
]);
}
else
{
op_p_cards
.
Add
(
cards
[
i
]);
}
}
}
}
List
<
gameCard
>
op_p_cards
=
realizeOpponentPendulumCards
;
gameField
.
mePHole
=
false
;
gameField
.
opPHole
=
false
;
...
...
@@ -8197,49 +8386,8 @@ public class Ocgcore : ServantWithCardDescription
if
(
Program
.
I
().
setting
.
setting
.
Vfield
.
value
)
{
int
code
=
0
;
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
if
(
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
&&
cards
[
i
].
p
.
sequence
==
5
)
{
if
(
cards
[
i
].
p
.
controller
==
0
)
{
if
((
cards
[
i
].
p
.
position
&
(
Int32
)
CardPosition
.
FaceUp
)
>
0
)
{
code
=
cards
[
i
].
get_data
().
Id
;
}
}
}
}
gameField
.
set
(
0
,
code
);
code
=
0
;
for
(
int
i
=
0
;
i
<
cards
.
Count
;
i
++)
if
(
cards
[
i
].
gameObject
.
activeInHierarchy
)
{
if
(
((
cards
[
i
].
p
.
location
&
(
UInt32
)
CardLocation
.
SpellZone
)
>
0
)
&&
cards
[
i
].
p
.
sequence
==
5
)
{
if
(
cards
[
i
].
p
.
controller
==
1
)
{
if
((
cards
[
i
].
p
.
position
&
(
Int32
)
CardPosition
.
FaceUp
)
>
0
)
{
code
=
cards
[
i
].
get_data
().
Id
;
}
}
}
}
gameField
.
set
(
1
,
code
);
gameField
.
set
(
0
,
myFieldCode
);
gameField
.
set
(
1
,
opFieldCode
);
}
else
{
...
...
@@ -8305,14 +8453,14 @@ public class Ocgcore : ServantWithCardDescription
{
gameInfo
.
removeHashedButton
(
"swap"
);
}
animation_count
(
gameField
.
LOCATION_DECK_0
,
CardLocation
.
Deck
,
0
);
animation_count
(
gameField
.
LOCATION_EXTRA_0
,
CardLocation
.
Extra
,
0
);
animation_count
(
gameField
.
LOCATION_GRAVE_0
,
CardLocation
.
Grave
,
0
);
animation_count
(
gameField
.
LOCATION_REMOVED_0
,
CardLocation
.
Removed
,
0
);
animation_count
(
gameField
.
LOCATION_DECK_1
,
CardLocation
.
Deck
,
1
);
animation_count
(
gameField
.
LOCATION_EXTRA_1
,
CardLocation
.
Extra
,
1
);
animation_count
(
gameField
.
LOCATION_GRAVE_1
,
CardLocation
.
Grave
,
1
);
animation_count
(
gameField
.
LOCATION_REMOVED_1
,
CardLocation
.
Removed
,
1
);
SetLocationCount
(
gameField
.
LOCATION_DECK_0
,
myDeckCount
,
0
,
CardLocation
.
Deck
);
SetLocationCount
(
gameField
.
LOCATION_EXTRA_0
,
myExtraCount
,
myExtraFaceUpCount
,
CardLocation
.
Extra
);
SetLocationCount
(
gameField
.
LOCATION_GRAVE_0
,
myGraveCount
,
0
,
CardLocation
.
Grave
);
SetLocationCount
(
gameField
.
LOCATION_REMOVED_0
,
myRemovedCount
,
0
,
CardLocation
.
Removed
);
SetLocationCount
(
gameField
.
LOCATION_DECK_1
,
opDeckCount
,
0
,
CardLocation
.
Deck
);
SetLocationCount
(
gameField
.
LOCATION_EXTRA_1
,
opExtraCount
,
opExtraFaceUpCount
,
CardLocation
.
Extra
);
SetLocationCount
(
gameField
.
LOCATION_GRAVE_1
,
opGraveCount
,
0
,
CardLocation
.
Grave
);
SetLocationCount
(
gameField
.
LOCATION_REMOVED_1
,
opRemovedCount
,
0
,
CardLocation
.
Removed
);
gameField
.
realize
();
Program
.
notGo
(
gameInfo
.
realize
);
Program
.
go
(
50
,
gameInfo
.
realize
);
...
...
@@ -8585,21 +8733,8 @@ public class Ocgcore : ServantWithCardDescription
}
}
}
if
(
count
<
2
)
{
textmesh
.
text
=
""
;
}
else
{
if
(
location
==
CardLocation
.
Extra
)
{
textmesh
.
text
=
count
.
ToString
()
+
"("
+
countU
.
ToString
()
+
")"
;
}
else
{
textmesh
.
text
=
count
.
ToString
();
}
}
SetLocationCount
(
textmesh
,
count
,
countU
,
location
);
}
float
camera_max
=
-
17.5f
;
...
...
@@ -8713,6 +8848,27 @@ public class Ocgcore : ServantWithCardDescription
public
List
<
gameCard
>
GCS_cardGetOverlayElements
(
gameCard
c
)
{
if
(
c
==
null
)
{
return
realizeToClearCards
;
}
if
((
c
.
p
.
location
&
(
UInt32
)
CardLocation
.
Overlay
)
>
0
)
{
return
realizeToClearCards
;
}
if
(
realizeOverlayMap
.
Count
>
0
)
{
List
<
gameCard
>
cached
=
GetOverlayElementsFromCache
(
c
);
if
(
cached
!=
null
)
{
return
cached
;
}
return
realizeEmptyOverlayList
;
}
List
<
gameCard
>
cas
=
new
List
<
gameCard
>();
if
(
c
!=
null
)
{
...
...
Assets/SibylSystem/Program.cs
View file @
d323f7a9
...
...
@@ -1592,7 +1592,7 @@ public class Program : MonoBehaviour
Running
=
false
;
try
{
TcpHelper
.
tcpClient
.
Close
(
);
TcpHelper
.
Disconnect
(
true
);
}
catch
(
Exception
e
)
{
...
...
Assets/SibylSystem/selectServer/SelectServer.cs
View file @
d323f7a9
...
...
@@ -291,10 +291,7 @@ public class SelectServer : WindowServantSP
Program
.
I
().
shiftToServant
(
Program
.
I
().
menu
);
if
(
TcpHelper
.
tcpClient
!=
null
)
{
if
(
TcpHelper
.
tcpClient
.
Connected
)
{
TcpHelper
.
tcpClient
.
Close
();
}
TcpHelper
.
Disconnect
(
true
);
}
}
...
...
docs/ocgcore-performance-and-stability-notes-2026-02-07.md
0 → 100644
View file @
d323f7a9
# OCGCore 性能与稳定性问题记录(2026-02-07)
## 背景
-
项目:
`ygopro2_unity2021`
(Unity 2021)
-
目标平台重点:iOS 移动端
-
当前阶段:已完成一轮 TCP 弱网优化与
`Ocgcore.cs`
重点性能排查
## 已完成的优化(当前工作区)
### 1) TCP 弱网优化(不改服务端协议)
-
文件:
`Assets/SibylSystem/MonoHelpers/TcpHelper.cs`
-
要点:
-
连接阶段参数更适配移动端(连接超时、KeepAlive 参数)
-
新增对局空闲期心跳(
`CtosMessage_TimeConfirm`
),避免“玩家思考无操作”期间误断线
-
发送失败增加短重试,提升瞬时抖动下的可用性
### 2) `realize()` 主流程降分配与降重复扫描
-
文件:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs`
-
要点:
-
将高频临时列表改为复用缓冲,减少 GC 压力
-
合并多处统计循环,单次遍历同时收集 deck/extra/grave/removed 计数
-
新增 overlay 映射缓存,避免反复 O(N²) 查找叠放素材
-
将库/额外/墓地/除外计数显示改为集中写入,减少重复调用
## 当前仍存在的主要性能热点
### P0:全量刷新触发频率高
-
位置:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:7412`
-
现象:
`realize()`
仍是全局大函数,且在消息处理中触发频繁。
-
风险:在移动端(尤其 iOS)高频对局消息下,主线程抖动明显。
### P0:按 GPS 查卡仍为线性扫描
-
位置:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:8789`
-
现象:
`GCS_cardGet()`
每次查找都遍历
`cards`
,对高频消息处理放大开销。
-
风险:对局越长、卡越多,查找成本越高。
### P1:局部统计函数重复扫全表
-
位置:
-
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:2803`
(
`countLocation`
)
-
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:2822`
(
`countLocationSequence`
)
-
现象:多处消息处理和 UI 刷新仍依赖全表计数。
### P1:整理阶段全量排序成本高
-
位置:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:9075`
-
现象:
`arrangeCards()`
进行全量
`Sort + 重写 sequence`
。
-
风险:若触发频率高,会出现额外 CPU 开销与潜在映射时序问题。
### P1:特效对象频繁 Instantiate/Destroy
-
位置示例:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:5722`
、
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:5762`
-
现象:指示物增减等消息内循环创建销毁特效对象。
-
风险:高频时造成 GC 与帧时间尖刺。
### P2:部分辅助函数仍有短生命周期 List 分配
-
位置:
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:6397`
、
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:6431`
-
现象:
`MHS_getBundle`
/
`MHS_resizeBundle`
仍使用即时新建列表。
## 功能稳定性风险(与“确认卡片”相关)
### ConfirmDecktop 定位来源不一致风险
-
位置:
-
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:2471`
-
`Assets/SibylSystem/Ocgcore/Ocgcore.cs:4811`
-
现象:读取了
`ReadShortGPS()`
,但后续定位卡片时使用“本地重算 sequence / 固定 Deck”的方式。
-
风险:当本地状态与消息发送时状态不完全同步,或确认来源并非严格 Deck 语义时,可能出现“展示与确认不是同一卡”的体验。
## 建议的后续执行顺序
1.
先做
`GCS_cardGet`
的索引化(字典缓存 + 变更点增量维护)
2.
再做
`realize()`
的脏区刷新(按 location/owner 增量更新)
3.
为 Confirm 系列消息统一“以消息 GPS 为唯一定位源”
4.
最后处理特效对象池与细节内存分配
## 验证建议(iOS/弱网)
-
使用 Network Link Conditioner / Charles /
`tc netem`
组合测试:
-
高延迟(150ms~500ms)、丢包(2%~10%)、抖动(50ms~150ms)
-
长时间“仅思考无操作”场景(5~10 分钟)
-
观察项:
-
是否误断线
-
心跳是否仅在对局空闲阶段触发
-
`realize()`
相关帧时间峰值与 GC 分配是否下降
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment