indexing description: "[ This is the server side of the client-server implementation of the multiplayer framework. This is a server that has the ability to let EM_NET_CLIENTs join. ]" date: "$Date$" revision: "$Revision$" class EM_NET_SERVER[TYPES->EM_NET_OBJECT_TYPES create make_and_initialize_factory end] inherit EM_NET_BASE[TYPES] rename make as make_base, send_update as send_update_to_clients redefine initialize_groups end create make feature -- Initialisation make (a_unique_game_identifier: STRING; a_port: INTEGER) is -- Create a new network server. -- `unique_game_identifier' is used by the master server to identifiy and manage servers of different games. -- This identifier must be chosen by hand. You need to ask the masterserver administrator wheter you can use -- a particular identifier or not (or you setup your own masterserver). require a_port_in_range: a_port >= 0 and then a_port < 65536 do game_identifier := a_unique_game_identifier make_base (a_port) protocol.set_is_time_master (True) name := "no name" heartbeat_periode := 2000 subscribe_by_type_id (object_types.em_net_server_discovery, agent on_server_discovery (?)) subscribe_by_type_id (object_types.em_net_leave_event, agent on_client_leave (?)) ensure open: is_open end feature -- Status change enable_sending_heartbeats is require not_enabled: not is_sending_heartbeats_enabled do is_sending_heartbeats_enabled := True timer.add_timed_procedure (agent on_send_heartbeat, 0) ensure is_sending_heartbeats_enabled_set: is_sending_heartbeats_enabled = True end disable_sending_heartbeats is -- Disable sending heartbeats. require not_disabled: is_sending_heartbeats_enabled do is_sending_heartbeats_enabled := False ensure is_sending_heartbeats_enabled_set: is_sending_heartbeats_enabled = False end enable_discovery_response is -- Enable discovery response. require not_enabled: not is_discovery_response_enabled do is_discovery_response_enabled := True ensure is_discovery_response_enabled_correct_set: is_discovery_response_enabled = True end disable_discovery_response is -- Disable discovery response. require not_disabled: is_discovery_response_enabled do is_discovery_response_enabled := False ensure is_discovery_response_enabled_correct_set: is_discovery_response_enabled = False end feature -- Object management add_and_publish_object (an_object: EM_NET_OBJECT; a_group: EM_NET_GROUP) is -- Add an object for synchronisation and publish it to all client in `a_group'. require an_object_not_void: an_object /= Void a_group_not_void: a_group /= Void local object_create_event: EM_NET_CREATE_OBJECT_RESPONSE do object_create_event := object_types.create_em_net_create_object_response object_create_event.set_object_to_create (an_object) object_create_event.set_group (a_group) object_create_event.set_success_agent (agent a_group.add_object (an_object)) object_create_event.set_timeout_agent (agent on_object_creation_timeout (an_object, a_group, ?)) object_create_event.publish end create_object_request_default_handler (a_request: EM_NET_CREATE_OBJECT_REQUEST) is -- Handle object creation request. -- This is a default handler that does the following: -- - Send an EM_NET_CREATE_OBJECT_RESPONSE to all clients in the group a_request.object_group_name -- - Create the object locally in the same group. -- To use this handler you need to subscribe to EM_NET_CREATE_OBJECT_REQUEST with this feature as agent or -- call it in your own agent. require a_request_not_void: a_request /= Void local sending_group: EM_NET_GROUP object: EM_NET_OBJECT do io.put_string ("Received object creation request. Broadcasting...%N") sending_group := group (a_request.create_group_name) object := net_object_factory.create_object_by_type_id (a_request.create_type_id) id_manager.set_unique_id_for_object (object) add_and_publish_object (object, sending_group) end remove_and_destroy_object (an_object: EM_NET_OBJECT) is -- Remove an object from synchronisation and publish a destroy event to all connection in the same group. require an_object_not_void: an_object /= Void local object_destroy_event: EM_NET_DESTROY_OBJECT_RESPONSE do an_object.group.remove_object (an_object) object_destroy_event := object_types.create_em_net_destroy_object_response object_destroy_event.set_destroy_id (an_object.id) object_destroy_event.set_group (an_object.group) object_destroy_event.set_timeout_agent (agent on_object_destruction_timeout (an_object.group, ?)) object_destroy_event.publish end destroy_object_request_default_handler (a_request: EM_NET_DESTROY_OBJECT_REQUEST) is -- Handle object destruction request. -- This is a default handler that does the following: -- - Send an EM_NET_DESTROY_OBJECT_RESPONSE to all clients in the group a_request.object_group_name -- - Remove the object locally from the same group. -- To use this handler you need to subscribe to EM_NET_DESTROY_OBJECT_REQUEST with this feature as agent or -- call it in your own agent. require request_not_void: a_request /= Void local old_object: EM_NET_OBJECT do old_object := objects.item (a_request.destroy_id) remove_and_destroy_object (old_object) end feature -- Element change set_name(a_name: STRING) is -- Set `name' to `a_name'. require a_name_not_void: a_name /= Void do name := a_name ensure name_set: name = a_name end feature -- Status information is_sending_heartbeats_enabled: BOOLEAN -- Is sending heartbeats to the master server enabled? is_discovery_response_enabled: BOOLEAN -- Is discovery response enabled? heartbeat_periode: INTEGER -- Periode between heartbeat messages to the master server masterserver_group: EM_NET_GROUP -- Masterserver group -- Add a masterserver to this group and he'll receive heartbeat's if sending heartbeats is enabled. name : STRING -- Server name -- This will be sent to the master server. Chose a meaningful name like: "Roxxor server" feature {NONE} -- Object management events on_object_creation_timeout (an_object: EM_NET_OBJECT; a_group: EM_NET_GROUP; timeout_connections: DS_LINKED_LIST[EM_NET_CONNECTION]) is -- Event that is called if a list of `timeout_connections' didn't acknowledge creation of `an_object' in `a_group'. -- By default this removes the connections from `a_group'. require object_not_void: an_object /= Void group_not_void: a_group /= Void timeout_connections_not_void: timeout_connections /= Void do from timeout_connections.start until timeout_connections.after loop timeout_connections.item_for_iteration.leave (a_group) timeout_connections.forth end a_group.add_object (an_object) end on_object_destruction_timeout (a_group: EM_NET_GROUP; timeout_connections: DS_LINKED_LIST[EM_NET_CONNECTION]) is -- Event that is called if a list of `timeout_connections' didn't acknowledge object destructoin in `a_group'. -- By default this removes the connections from `a_group'. require group_not_void: a_group /= Void timeout_connections_not_void: timeout_connections /= Void do from timeout_connections.start until timeout_connections.after loop timeout_connections.item_for_iteration.leave (a_group) timeout_connections.forth end end feature {NONE} -- Client events on_new_connection (a_socket: EM_INET_SOCKET_ADDRESS) is -- A so far unknown peer sent us a packet. do create_connection (a_socket) end on_client_leave (a_leave_event: EM_NET_LEAVE_EVENT) is -- Event that is fired if a client left. local connection: EM_NET_CONNECTION group_cursor: DS_LINKED_LIST_CURSOR[EM_NET_GROUP] do connection := a_leave_event.updating_connection dynamic_connection_list.delete (connection) group_cursor := connection.groups.new_cursor from group_cursor.start until group_cursor.after loop connection.leave (group_cursor.item) group_cursor.forth end remove_group (connection.personal_group) ensure all_groups_left: a_leave_event.updating_connection.groups.count = 0 personal_group_removed: not group_exists (a_leave_event.updating_connection.personal_group) end on_client_timeout (a_client: EM_NET_CLIENT_OBJECT) is -- Event that is fired if a client did not respond for a certain time. do end on_server_discovery (a_scan: EM_NET_SERVER_DISCOVERY) is -- Server discovery message recieved. local discovery_response: EM_NET_SERVER_DISCOVERY_RESPONSE do if is_discovery_response_enabled and then a_scan.game_name.is_equal (game_identifier) then discovery_response := object_types.create_em_net_server_discovery_response discovery_response.set_group (a_scan.group) discovery_response.set_game_name (game_identifier) discovery_response.set_server_name (name) discovery_response.publish end end on_send_heartbeat is -- Send a heartbeat to the masterserver so he can sort out inactive servers automatically local heartbeat: EM_NET_STATUS_MESSAGE do if is_sending_heartbeats_enabled then heartbeat := object_types.create_em_net_status_message heartbeat.set_status ("extron::HEARTBEAT::" + game_identifier +"::"+ name +"::" + "servertime: " + time.out+"::extron") heartbeat.set_group (masterserver_group) heartbeat.publish timer.add_timed_procedure (agent on_send_heartbeat, heartbeat_periode) end end feature {NONE} -- Implementation initialize_groups is -- Initialize groups. do Precursor create masterserver_group.make ("MS", me) end invariant id_manager_not_void: id_manager /= Void end