mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Implement basic virtual Room support based on enet (#2803)
* Added support for network with ENet lib, connecting is possible, but data can't be sent, yet. * fixup! Added support for network with ENet lib, * fixup! CLang * fixup! Added support for network with ENet lib, * fixup! Added support for network with ENet lib, * fixup! Clang format * More fixups! * Moved ENetHost* and ENetPeer* into pimpl classes * fixup! Moved ENetHost* and ENetPeer* into pimpl classes * fixup! Clang again * fixup! Moved ENetHost* and ENetPeer* into pimpl classes * fixup! Moved ENetHost* and ENetPeer* into pimpl classes * fixup! Moved ENetHost* and ENetPeer* into pimpl classes
This commit is contained in:
		
							parent
							
								
									22e7402ab1
								
							
						
					
					
						commit
						2e37ce01c9
					
				
					 15 changed files with 365 additions and 1 deletions
				
			
		|  | @ -5,6 +5,7 @@ add_subdirectory(common) | |||
| add_subdirectory(core) | ||||
| add_subdirectory(video_core) | ||||
| add_subdirectory(audio_core) | ||||
| add_subdirectory(network) | ||||
| add_subdirectory(input_common) | ||||
| add_subdirectory(tests) | ||||
| if (ENABLE_SDL2) | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ if (APPLE) | |||
| else() | ||||
|     add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) | ||||
| endif() | ||||
| target_link_libraries(citra-qt PRIVATE audio_core common core input_common video_core) | ||||
| target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core) | ||||
| target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets) | ||||
| target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| #include "core/settings.h" | ||||
| #include "input_common/keyboard.h" | ||||
| #include "input_common/main.h" | ||||
| #include "network/network.h" | ||||
| 
 | ||||
| EmuThread::EmuThread(GRenderWindow* render_window) | ||||
|     : exec_step(false), running(false), stop_run(false), render_window(render_window) {} | ||||
|  | @ -110,10 +111,12 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | |||
|     setWindowTitle(QString::fromStdString(window_title)); | ||||
| 
 | ||||
|     InputCommon::Init(); | ||||
|     Network::Init(); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::~GRenderWindow() { | ||||
|     InputCommon::Shutdown(); | ||||
|     Network::Shutdown(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::moveContext() { | ||||
|  |  | |||
|  | @ -72,6 +72,7 @@ namespace Log { | |||
|     SUB(Audio, DSP)                                                                                \ | ||||
|     SUB(Audio, Sink)                                                                               \ | ||||
|     CLS(Input)                                                                                     \ | ||||
|     CLS(Network)                                                                                   \ | ||||
|     CLS(Loader) | ||||
| 
 | ||||
| // GetClassName is a macro defined by Windows.h, grrr...
 | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ enum class Class : ClassType { | |||
|     Audio_Sink,        ///< Emulator audio output backend
 | ||||
|     Loader,            ///< ROM loader
 | ||||
|     Input,             ///< Input emulation
 | ||||
|     Network,           ///< Network emulation
 | ||||
|     Count              ///< Total number of logging classes
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										16
									
								
								src/network/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/network/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| set(SRCS | ||||
|             network.cpp | ||||
|             room.cpp | ||||
|             room_member.cpp | ||||
|             ) | ||||
| 
 | ||||
| set(HEADERS | ||||
|             network.h | ||||
|             room.h | ||||
|             room_member.h | ||||
|             ) | ||||
| 
 | ||||
| create_directory_groups(${SRCS} ${HEADERS}) | ||||
| 
 | ||||
| add_library(network STATIC ${SRCS} ${HEADERS}) | ||||
| target_link_libraries(network PRIVATE common enet) | ||||
							
								
								
									
										50
									
								
								src/network/network.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/network/network.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "enet/enet.h" | ||||
| #include "network/network.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
 | ||||
| static std::shared_ptr<Room> g_room;              ///< Room (Server) for network games
 | ||||
| // TODO(B3N30): Put these globals into a networking class
 | ||||
| 
 | ||||
| bool Init() { | ||||
|     if (enet_initialize() != 0) { | ||||
|         LOG_ERROR(Network, "Error initalizing ENet"); | ||||
|         return false; | ||||
|     } | ||||
|     g_room = std::make_shared<Room>(); | ||||
|     g_room_member = std::make_shared<RoomMember>(); | ||||
|     LOG_DEBUG(Network, "initialized OK"); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::weak_ptr<Room> GetRoom() { | ||||
|     return g_room; | ||||
| } | ||||
| 
 | ||||
| std::weak_ptr<RoomMember> GetRoomMember() { | ||||
|     return g_room_member; | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
|     if (g_room_member) { | ||||
|         if (g_room_member->IsConnected()) | ||||
|             g_room_member->Leave(); | ||||
|         g_room_member.reset(); | ||||
|     } | ||||
|     if (g_room) { | ||||
|         if (g_room->GetState() == Room::State::Open) | ||||
|             g_room->Destroy(); | ||||
|         g_room.reset(); | ||||
|     } | ||||
|     enet_deinitialize(); | ||||
|     LOG_DEBUG(Network, "shutdown OK"); | ||||
| } | ||||
| 
 | ||||
| } // namespace Network
 | ||||
							
								
								
									
										25
									
								
								src/network/network.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/network/network.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include "network/room.h" | ||||
| #include "network/room_member.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| /// Initializes and registers the network device, the room, and the room member.
 | ||||
| bool Init(); | ||||
| 
 | ||||
| /// Returns a pointer to the room handle
 | ||||
| std::weak_ptr<Room> GetRoom(); | ||||
| 
 | ||||
| /// Returns a pointer to the room member handle
 | ||||
| std::weak_ptr<RoomMember> GetRoomMember(); | ||||
| 
 | ||||
| /// Unregisters the network device, the room, and the room member and shut them down.
 | ||||
| void Shutdown(); | ||||
| 
 | ||||
| } // namespace Network
 | ||||
							
								
								
									
										60
									
								
								src/network/room.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/network/room.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "enet/enet.h" | ||||
| #include "network/room.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| /// Maximum number of concurrent connections allowed to this room.
 | ||||
| static constexpr u32 MaxConcurrentConnections = 10; | ||||
| 
 | ||||
| class Room::RoomImpl { | ||||
| public: | ||||
|     ENetHost* server = nullptr; ///< Network interface.
 | ||||
| 
 | ||||
|     std::atomic<State> state{State::Closed}; ///< Current state of the room.
 | ||||
|     RoomInformation room_information;        ///< Information about this room.
 | ||||
| }; | ||||
| 
 | ||||
| Room::Room() : room_impl{std::make_unique<RoomImpl>()} {} | ||||
| 
 | ||||
| Room::~Room() = default; | ||||
| 
 | ||||
| void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) { | ||||
|     ENetAddress address; | ||||
|     address.host = ENET_HOST_ANY; | ||||
|     enet_address_set_host(&address, server_address.c_str()); | ||||
|     address.port = server_port; | ||||
| 
 | ||||
|     room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); | ||||
|     // TODO(B3N30): Allow specifying the maximum number of concurrent connections.
 | ||||
|     room_impl->state = State::Open; | ||||
| 
 | ||||
|     room_impl->room_information.name = name; | ||||
|     room_impl->room_information.member_slots = MaxConcurrentConnections; | ||||
| 
 | ||||
|     // TODO(B3N30): Start the receiving thread
 | ||||
| } | ||||
| 
 | ||||
| Room::State Room::GetState() const { | ||||
|     return room_impl->state; | ||||
| } | ||||
| 
 | ||||
| const RoomInformation& Room::GetRoomInformation() const { | ||||
|     return room_impl->room_information; | ||||
| } | ||||
| 
 | ||||
| void Room::Destroy() { | ||||
|     room_impl->state = State::Closed; | ||||
|     // TODO(B3n30): Join the receiving thread
 | ||||
| 
 | ||||
|     if (room_impl->server) { | ||||
|         enet_host_destroy(room_impl->server); | ||||
|     } | ||||
|     room_impl->room_information = {}; | ||||
|     room_impl->server = nullptr; | ||||
| } | ||||
| 
 | ||||
| } // namespace Network
 | ||||
							
								
								
									
										60
									
								
								src/network/room.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/network/room.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| constexpr u16 DefaultRoomPort = 1234; | ||||
| constexpr size_t NumChannels = 1; // Number of channels used for the connection
 | ||||
| 
 | ||||
| struct RoomInformation { | ||||
|     std::string name; ///< Name of the server
 | ||||
|     u32 member_slots; ///< Maximum number of members in this room
 | ||||
| }; | ||||
| 
 | ||||
| /// This is what a server [person creating a server] would use.
 | ||||
| class Room final { | ||||
| public: | ||||
|     enum class State : u8 { | ||||
|         Open,   ///< The room is open and ready to accept connections.
 | ||||
|         Closed, ///< The room is not opened and can not accept connections.
 | ||||
|     }; | ||||
| 
 | ||||
|     Room(); | ||||
|     ~Room(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the current state of the room. | ||||
|      */ | ||||
|     State GetState() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the room information of the room. | ||||
|      */ | ||||
|     const RoomInformation& GetRoomInformation() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates the socket for this room. Will bind to default address if | ||||
|      * server is empty string. | ||||
|      */ | ||||
|     void Create(const std::string& name, const std::string& server = "", | ||||
|                 u16 server_port = DefaultRoomPort); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Destroys the socket | ||||
|      */ | ||||
|     void Destroy(); | ||||
| 
 | ||||
| private: | ||||
|     class RoomImpl; | ||||
|     std::unique_ptr<RoomImpl> room_impl; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Network
 | ||||
							
								
								
									
										74
									
								
								src/network/room_member.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/network/room_member.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "enet/enet.h" | ||||
| #include "network/room_member.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| constexpr u32 ConnectionTimeoutMs = 5000; | ||||
| 
 | ||||
| class RoomMember::RoomMemberImpl { | ||||
| public: | ||||
|     ENetHost* client = nullptr; ///< ENet network interface.
 | ||||
|     ENetPeer* server = nullptr; ///< The server peer the client is connected to
 | ||||
| 
 | ||||
|     std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
 | ||||
| 
 | ||||
|     std::string nickname; ///< The nickname of this member.
 | ||||
| }; | ||||
| 
 | ||||
| RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} { | ||||
|     room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); | ||||
|     ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client"); | ||||
| } | ||||
| 
 | ||||
| RoomMember::~RoomMember() { | ||||
|     ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected"); | ||||
|     enet_host_destroy(room_member_impl->client); | ||||
| } | ||||
| 
 | ||||
| RoomMember::State RoomMember::GetState() const { | ||||
|     return room_member_impl->state; | ||||
| } | ||||
| 
 | ||||
| void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, | ||||
|                       u16 client_port) { | ||||
|     ENetAddress address{}; | ||||
|     enet_address_set_host(&address, server_addr); | ||||
|     address.port = server_port; | ||||
| 
 | ||||
|     room_member_impl->server = | ||||
|         enet_host_connect(room_member_impl->client, &address, NumChannels, 0); | ||||
| 
 | ||||
|     if (!room_member_impl->server) { | ||||
|         room_member_impl->state = State::Error; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ENetEvent event{}; | ||||
|     int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs); | ||||
|     if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { | ||||
|         room_member_impl->nickname = nick; | ||||
|         room_member_impl->state = State::Joining; | ||||
|         // TODO(B3N30): Send a join request with the nickname to the server
 | ||||
|         // TODO(B3N30): Start the receive thread
 | ||||
|     } else { | ||||
|         room_member_impl->state = State::CouldNotConnect; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool RoomMember::IsConnected() const { | ||||
|     return room_member_impl->state == State::Joining || room_member_impl->state == State::Joined; | ||||
| } | ||||
| 
 | ||||
| void RoomMember::Leave() { | ||||
|     enet_peer_disconnect(room_member_impl->server, 0); | ||||
|     room_member_impl->state = State::Idle; | ||||
|     // TODO(B3N30): Close the receive thread
 | ||||
|     enet_peer_reset(room_member_impl->server); | ||||
| } | ||||
| 
 | ||||
| } // namespace Network
 | ||||
							
								
								
									
										65
									
								
								src/network/room_member.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/network/room_member.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| #include "network/room.h" | ||||
| 
 | ||||
| namespace Network { | ||||
| 
 | ||||
| /**
 | ||||
|  * This is what a client [person joining a server] would use. | ||||
|  * It also has to be used if you host a game yourself (You'd create both, a Room and a | ||||
|  * RoomMembership for yourself) | ||||
|  */ | ||||
| class RoomMember final { | ||||
| public: | ||||
|     enum class State : u8 { | ||||
|         Idle,    ///< Default state
 | ||||
|         Error,   ///< Some error [permissions to network device missing or something]
 | ||||
|         Joining, ///< The client is attempting to join a room.
 | ||||
|         Joined,  ///< The client is connected to the room and is ready to send/receive packets.
 | ||||
|         LostConnection, ///< Connection closed
 | ||||
| 
 | ||||
|         // Reasons why connection was rejected
 | ||||
|         NameCollision,  ///< Somebody is already using this name
 | ||||
|         MacCollision,   ///< Somebody is already using that mac-address
 | ||||
|         CouldNotConnect ///< The room is not responding to a connection attempt
 | ||||
|     }; | ||||
| 
 | ||||
|     RoomMember(); | ||||
|     ~RoomMember(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns the status of our connection to the room. | ||||
|      */ | ||||
|     State GetState() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns whether we're connected to a server or not. | ||||
|      */ | ||||
|     bool IsConnected() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Attempts to join a room at the specified address and port, using the specified nickname. | ||||
|      * This may fail if the username is already taken. | ||||
|      */ | ||||
|     void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", | ||||
|               const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Leaves the current room. | ||||
|      */ | ||||
|     void Leave(); | ||||
| 
 | ||||
| private: | ||||
|     class RoomMemberImpl; | ||||
|     std::unique_ptr<RoomMemberImpl> room_member_impl; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Network
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue