Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Minigame refact & r2dbc 추가 #21

Merged
merged 22 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ dependencies {
testImplementation 'io.projectreactor:reactor-test'

implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive:3.1.2'
// implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64'

testImplementation('org.junit.jupiter:junit-jupiter:5.5.0')
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")

implementation "org.springframework.boot:spring-boot-starter-data-r2dbc"
implementation "com.github.jasync-sql:jasync-r2dbc-mysql:2.2.0"

//JUnit4 추가
/*testImplementation("org.junit.vintage:junit-vintage-engine") {
exclude group: "org.hamcrest", module: "hamcrest-core"
Expand Down
1 change: 1 addition & 0 deletions src/main/java/kutaverse/game/DemoApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/kutaverse/game/minigame/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package kutaverse.game.minigame.domain;


import kutaverse.game.websocket.minigame.dto.GameUpdateDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;

@Table("game_results")
@Builder
@NoArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPA처럼 엔티티 mapping이 되나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

찾아보니 매핑이 되는 것으로 확인하였습니다. 매핑은 되나 DDL이 지원되지는 않는 것 같습니다. 만약 DB에 해당 테이블이 있다면 매핑은 됩니다.

@AllArgsConstructor
public class GameResult {
@Id
private Long id;

@Column("player1_id")
private String player1Id;

@Column("player2_id")
private String player2Id;

@Column("player1_score")
private int player1Score;

@Column("player2_score")
private int player2Score;

@Column("winner_id")
private String winnerId;

@Column("room_id")
private String roomId;

@CreatedDate
@Column("created_at")
private LocalDateTime createdAt;

@LastModifiedDate
@Column("updated_at")
private LocalDateTime updatedAt;


public void update(GameUpdateDTO gameUpdateDTO){
player1Score = gameUpdateDTO.getPlayer1Score();
player2Score = gameUpdateDTO.getPlayer2Score();
winnerId = gameUpdateDTO.getWinnerId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kutaverse.game.minigame.repository;

import kutaverse.game.minigame.domain.GameResult;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface MiniGameReactiveRepository extends ReactiveCrudRepository<GameResult,Long> {

Flux<GameResult> findByPlayer1Id(String userId);

Mono<GameResult> findByRoomId(String id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kutaverse.game.minigame.repository;

import kutaverse.game.minigame.domain.GameResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface MiniGameRepository {
// 해당 방 게임 결과의 데이터 가져오기
Mono<GameResult> findByRoomId(String id);

// 방 생성 시 초기화
Mono<GameResult> save(GameResult gameResult);

// 게임 종료 시 방 데이터 변경
Mono<GameResult> update(GameResult gameResult);

// 해당 유저 게임 정보 가져오기
Flux<GameResult> findByPlayerId(String userId);

// 해당 데이터 삭제
Mono<Void> delete(Long id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kutaverse.game.minigame.repository;

import kutaverse.game.minigame.domain.GameResult;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Repository
@RequiredArgsConstructor
public class MiniGameRepositoryImpl implements MiniGameRepository{
private final MiniGameReactiveRepository mgrRepository;

@Override
public Mono<GameResult> findByRoomId(String id) {
return mgrRepository.findByRoomId(id);
}

@Override
public Mono<GameResult> save(GameResult gameResult) {
return mgrRepository.save(gameResult);
}

@Override
public Mono<GameResult> update(GameResult gameResult) {
return mgrRepository.save(gameResult);
}

@Override
public Flux<GameResult> findByPlayerId(String userId) {
return mgrRepository.findByPlayer1Id(userId);
}

@Override
public Mono<Void> delete(Long id) {
return mgrRepository.deleteById(id);
}
}
19 changes: 19 additions & 0 deletions src/main/java/kutaverse/game/minigame/service/MiniGameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kutaverse.game.minigame.service;

import kutaverse.game.minigame.domain.GameResult;
import kutaverse.game.websocket.minigame.dto.GameResultDTO;
import kutaverse.game.websocket.minigame.dto.GameUpdateDTO;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface MiniGameService {
Mono<GameResult> createGameResult(GameResultDTO gameResultDTO);

Mono<GameResult> updateGameResult(GameUpdateDTO gameUpdateDTO);

Mono<Void> deleteGameResult(Long id);

Flux<GameResult> findGameResultsByPlayerId(String playerId);

Mono<GameResult> findGameResultById(String id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package kutaverse.game.minigame.service;

import kutaverse.game.minigame.domain.GameResult;
import kutaverse.game.minigame.repository.MiniGameReactiveRepository;
import kutaverse.game.minigame.repository.MiniGameRepository;
import kutaverse.game.websocket.minigame.dto.GameResultDTO;
import kutaverse.game.websocket.minigame.dto.GameUpdateDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@Service
@Slf4j
@RequiredArgsConstructor
public class MiniGameServiceImpl implements MiniGameService{
private final MiniGameRepository mgrRepository ;

@Override
public Mono<GameResult> createGameResult(GameResultDTO gameResultDTO) {
GameResult gameResult = gameResultDTO.toEntity();
return mgrRepository.save(gameResult);
}

@Transactional
public Mono<GameResult> updateGameResult(GameUpdateDTO gameUpdateDTO){
return mgrRepository.findByRoomId(gameUpdateDTO.getRoomId())
.flatMap(gameResult -> {
gameResult.update(gameUpdateDTO);
Comment on lines +31 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flatMap은 무적인가요? 해당 코드에서 Map을 쓰면 안되나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 리액티브 프로그래밍이 익숙하지 않아 거의 flatMap에 의존하여 사용하고 있는거 같습니다(거의 무적으로 쓰는듯...). 저번에 말해주신 것처럼 코스트가 크다고 했던 걸로 기억하여 대체할만한 것을 찾아 리팩토링 해보겠습니다.

log.info(String.valueOf(gameUpdateDTO.getPlayer1Score()));
return mgrRepository.save(gameResult);
});
}


@Override
public Mono<Void> deleteGameResult(Long id) {
return mgrRepository.delete(id);
}

@Override
public Flux<GameResult> findGameResultsByPlayerId(String playerId) {
return mgrRepository.findByPlayerId(playerId);
}

@Override
public Mono<GameResult> findGameResultById(String id) {
return mgrRepository.findByRoomId(id);
}
}
10 changes: 10 additions & 0 deletions src/main/java/kutaverse/game/websocket/minigame/GameRoom.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,14 @@ public void sendDataToOther(String userId, MiniGameRequest data) throws JsonProc
session.send(Mono.just(session.textMessage(dataJson))).subscribe();
});
}

// 상대방이 나간 처리
public void handlePlayerLeft(String userId){
players.entrySet().stream()
.filter(entry -> !entry.getKey().equals(userId))
.forEach(entry -> {
WebSocketSession session = entry.getValue();
session.send(Mono.just(session.textMessage("상대방이 나갔습니다."))).subscribe();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import kutaverse.game.minigame.dto.MiniGameRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class GameRoomManager {
public static final Map<String, GameRoom> gameRooms = new ConcurrentHashMap<>();

Expand All @@ -26,4 +30,18 @@ public static void sendPlayerData(String roomId, String userId, MiniGameRequest
GameRoom gameRoom = getGameRoom(roomId);
gameRoom.sendDataToOther(userId,data);
}

@Scheduled(fixedDelay = 5000) // 5초마다 실행
public void checkPlayerStatus() {
for (Map.Entry<String, GameRoom> entry : gameRooms.entrySet()) {
GameRoom gameRoom = entry.getValue();
for (Map.Entry<String, WebSocketSession> playerEntry : gameRoom.getPlayers().entrySet()) {
String userId = playerEntry.getKey();
WebSocketSession session = playerEntry.getValue();
if (!session.isOpen()) { // WebSocket 연결이 닫혔는지 확인
gameRoom.handlePlayerLeft(userId);
}
}
}
}
}
14 changes: 12 additions & 2 deletions src/main/java/kutaverse/game/websocket/minigame/MatchingMaker.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ public class MatchingMaker {

@Scheduled(fixedRate = 1000)
public void matchPlayers(){
log.info(String.valueOf(MatchingQueue.queueingSize()));
// log.info(String.valueOf(MatchingQueue.queueingSize()));
while(MatchingQueue.queueingSize() >= 2){
Map.Entry<String, WebSocketSession> player1 = MatchingQueue.getPlayer();
Map.Entry<String, WebSocketSession> player2 = MatchingQueue.getPlayer();

if (player1 != null && player2 != null) {
// 세션 값 확인 필요
if(player1.getValue().isOpen() && player2.getValue().isOpen()){
createGameRoom(player1, player2);
}
else{
if(player1.getValue().isOpen()){
MatchingQueue.requeue(player1);
}
if(player2.getValue().isOpen()){
MatchingQueue.requeue(player2);
}
}

}
}

Expand Down
17 changes: 13 additions & 4 deletions src/main/java/kutaverse/game/websocket/minigame/MatchingQueue.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
import org.springframework.web.reactive.socket.WebSocketSession;

import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;

// Queue를 통해 플레이어들을 저장해 둘 예정


@Component
public class MatchingQueue {
private static final Queue<Map.Entry<String, WebSocketSession>> queueing = new ConcurrentLinkedQueue<>();
private static final ArrayDeque<Map.Entry<String, WebSocketSession>> queueing = new ArrayDeque<>();
private static final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();

public static void addPlayer(String userId, WebSocketSession webSocketSession){
queueing.offer(new AbstractMap.SimpleEntry<>(userId,webSocketSession));
if(!sessionMap.containsKey(userId)) {
queueing.offer(new AbstractMap.SimpleEntry<>(userId,webSocketSession));
sessionMap.put(userId,webSocketSession);
}

}

public static Map.Entry<String, WebSocketSession> getPlayer(){
Expand All @@ -27,5 +32,9 @@ public static int queueingSize(){
return queueing.size();
}

public static void requeue(Map.Entry<String, WebSocketSession> player){
queueing.addFirst(player);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kutaverse.game.websocket.minigame.dto;

import kutaverse.game.minigame.domain.GameResult;
import lombok.AllArgsConstructor;
import org.springframework.data.relational.core.mapping.Column;

@AllArgsConstructor
public class GameResultDTO {
private String player1Id;

private String player2Id;

private String roomId;

public GameResult toEntity(){
return GameResult.builder()
.player1Id(player1Id)
.player2Id(player2Id)
.roomId(roomId)
.build();
}
}
Loading
Loading