diff --git a/hwe/sammo/Message.php b/hwe/sammo/Message.php index 1a140d4f..aca0c872 100644 --- a/hwe/sammo/Message.php +++ b/hwe/sammo/Message.php @@ -15,6 +15,9 @@ class Message protected $sendCnt = 0; + private ?int $sendTimeTick = null; + private ?int $sendValidUntilTick = null; + public function __construct( public MessageType $msgType, public MessageTarget $src, @@ -324,15 +327,7 @@ class Message $db = DB::db(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env')); - $timeTick = $clock->nowTick(); - if (Util::toInt($this->validUntil->format('Y')) >= 9000) { - $validUntilTick = GameClock::MAX_SAFE_TICK; - } else { - $validitySeconds = $this->validUntil->getTimestamp() - $this->date->getTimestamp(); - $validUntilTick = $timeTick + $clock->ticksFromSeconds($validitySeconds); - } - $this->date = \DateTime::createFromImmutable($clock->tickToDateTime($timeTick)); - $this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick)); + [$timeTick, $validUntilTick] = $this->resolveSendTicks($clock); $db->insert('message', [ 'mailbox' => $mailbox, 'type' => $this->msgType->value, @@ -350,6 +345,32 @@ class Message return [$mailbox, $db->insertId()]; } + /** @return array{0:int, 1:int} */ + protected function resolveSendTicks(GameClock $clock): array + { + if ($this->sendTimeTick !== null && $this->sendValidUntilTick !== null) { + return [$this->sendTimeTick, $this->sendValidUntilTick]; + } + + $timeTick = $clock->nowTick(); + if (Util::toInt($this->validUntil->format('Y')) >= 9000) { + $validUntilTick = GameClock::MAX_SAFE_TICK; + } else { + $validitySeconds = $this->validUntil->getTimestamp() - $this->date->getTimestamp(); + $validUntilTick = GameClock::addTicks( + $timeTick, + $clock->ticksFromSeconds($validitySeconds), + ); + } + + $this->sendTimeTick = $timeTick; + $this->sendValidUntilTick = $validUntilTick; + $this->date = \DateTime::createFromImmutable($clock->tickToDateTime($timeTick)); + $this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick)); + + return [$timeTick, $validUntilTick]; + } + private function sendToSender():array{ if($this->sendCnt > 1){ throw new \RuntimeException('이미 전송한 메일입니다.'); diff --git a/tests/MessageGameClockTest.php b/tests/MessageGameClockTest.php new file mode 100644 index 00000000..96f3c064 --- /dev/null +++ b/tests/MessageGameClockTest.php @@ -0,0 +1,95 @@ +createMock(MessageTarget::class), + $this->createMock(MessageTarget::class), + '선전포고', + new \DateTime('2026-08-04 00:00:00.000000'), + new \DateTime('9999-12-31'), + [], + ); + + $receiverTicks = $message->resolveTicksForTest($clock); + self::assertLessThan(9000, Util::toInt($message->validUntil->format('Y'))); + $legacySecondClock = new GameClock( + $base, + 1, + 0, + GameClock::MODE_REALTIME, + $base, + static fn (): \DateTimeImmutable => $wallTimes[1], + ); + $legacySecondNowTick = $legacySecondClock->nowTick(); + self::assertSame(562_884, $legacySecondNowTick); + $legacySecondExpiry = $legacySecondNowTick + $clock->ticksFromSeconds( + $message->validUntil->getTimestamp() - $message->date->getTimestamp(), + ); + self::assertSame(9_007_199_254_762_884, $legacySecondExpiry); + $senderTicks = $message->resolveTicksForTest($clock); + + self::assertSame([0, GameClock::MAX_SAFE_TICK], $receiverTicks); + self::assertSame($receiverTicks, $senderTicks); + self::assertSame(1, $wallReads); + } + + public function testFiniteMessageCachesOneValidatedExpiryForBothCopies(): void + { + $base = new \DateTimeImmutable('2026-08-04 00:00:00.000000'); + $clock = new GameClock($base, 1, 120_000, GameClock::MODE_MANUAL, $base); + $message = new TestableClockMessage( + MessageType::national, + $this->createMock(MessageTarget::class), + $this->createMock(MessageTarget::class), + '유한 메시지', + new \DateTime('2026-08-04 00:00:02.000000'), + new \DateTime('2026-08-04 00:01:02.000000'), + [], + ); + + $receiverTicks = $message->resolveTicksForTest($clock); + $senderTicks = $message->resolveTicksForTest($clock); + + self::assertSame([120_000, 36_120_000], $receiverTicks); + self::assertSame($receiverTicks, $senderTicks); + } +} + +final class TestableClockMessage extends Message +{ + /** @return array{0:int, 1:int} */ + public function resolveTicksForTest(GameClock $clock): array + { + return $this->resolveSendTicks($clock); + } +}