indexing description: "[ The protocol that is used for network communication. This is the lowest layer in the MP framework, just on top of the actual sockets. Message format: Header: Message prefix: STRING, 5 bytes (see `message_prefix') Version: INTEGER_8, 1 byte (see `version') Time object: STRING Data (may be repeated): Object ID: INTEGER, 4 bytes Data: STRING Short: [PREFIX|VERSION|[ID|DATA]*] ]" date: "$Date$" revision: "$Revision$" class EM_NET_PROTOCOL inherit EM_TIME_SINGLETON rename time as time_singleton export {NONE} all end EM_NETWORK_HELPER_FUNCTIONS export {NONE} all end EM_NET_OBJECT_TYPES rename make_and_initialize_factory as dont_execute_me export {NONE} all end EM_NETWORK_CONSTANTS create make feature {NONE} -- Initialization make is -- Initialize the protocol. do time_sync_period := 5000 correction_factor := 0.2 time_sync_agent := agent send_time_sync_request set_max_serialization_byte_count (max_udp_packet_size-15) ensure time_sync_agent_set: time_sync_agent /= Void not_is_time_master: not is_time_master max_serialization_byte_count_positive: max_serialization_byte_count.item > 0 end feature -- Receive parse_packet (a_packet: EM_UDP_PACKET) is -- Parse client message and fire the object received event. require not_too_long_input: a_packet.item.count <= max_udp_packet_size local ok: BOOLEAN input: EM_NET_UNSERIALIZER object_id: INTEGER packet_time: EM_NET_TIME_OBJECT do create input.make (a_packet.item) from -- Read prefix input.read_string (5) ok := input.last_string.is_equal (message_prefix) -- Read version input.read_character ok := ok and input.last_character.code = version if ok then -- Read time object packet_time := create_em_net_time_object packet_time.unserialize (input) end until input.is_everything_read or not ok loop -- Read object ID input.read_integer object_id := input.last_integer -- Fire event object_update_handler.call ([packet_time.item,object_id,input]) end end feature -- Send create_packet (net_objects: DS_LINKED_LIST [EM_NET_OBJECT]; serializer: EM_NET_SERIALIZER) is -- Create a new message and make it available in `last_message'. local time_object: EM_NET_TIME_OBJECT at_least_one_object_serialized: BOOLEAN do time_object := create_em_net_time_object time_object.set_item (time) serializer.string.wipe_out serializer.put_string (message_prefix) serializer.put_character (version.to_character) time_object.serialize (serializer) from until net_objects.is_empty or else serializer.string.count + net_objects.first.serialization_byte_count >= max_udp_packet_size loop serializer.put_integer (net_objects.first.id) net_objects.first.serialize (serializer) net_objects.remove_first at_least_one_object_serialized := true end check at_least_one_object_serialized: at_least_one_object_serialized end create last_packet.make last_packet.put_string (serializer.string) ensure not_too_long_output: last_packet.item.count <= max_udp_packet_size end feature -- Time synchronisation send_time_sync_request is -- Send a time sync request. -- The request will be sent to the group which is set by the feature set_time_sync_group. local a_time_sync_request: EM_NET_TIME_SYNC_REQUEST do current_time_sync_request_id := current_time_sync_request_id + 1 current_time_sync_start_ticks:=time_singleton.ticks a_time_sync_request := create_em_net_time_sync_request a_time_sync_request.set_group (time_sync_group) a_time_sync_request.set_request_id (current_time_sync_request_id) a_time_sync_request.publish if is_time_sync_enabled then time_singleton.add_timed_procedure(time_sync_agent, volatile_period) end -- Auto increase the period between time syncs if volatile_period < time_sync_period then volatile_period := volatile_period * 2 else volatile_period := time_sync_period end debug("EM_MP") io.put_string("on send sync request with volatile of " + volatile_period.out + "%N") end end on_time_sync_request(a_request: EM_NET_TIME_SYNC_REQUEST) is -- Time sync request event require a_request /= Void local a_time_sync_response: EM_NET_TIME_SYNC_RESPONSE do a_time_sync_response := create_em_net_time_sync_response a_time_sync_response.set_request_id (a_request.request_id) a_time_sync_response.set_time(time) a_time_sync_response.set_is_from_master_node (is_time_master) a_time_sync_response.set_group (a_request.group) a_time_sync_response.publish debug("EM_MP") io.put_string("on sync request%N") end end on_time_sync_response(a_response: EM_NET_TIME_SYNC_RESPONSE) is -- Time sync response event local tmp_result: DOUBLE do if a_response.request_id = current_time_sync_request_id then if last_time = 0 then -- The first time we got a sync. This is certainly valued a bit to high. tmp_result := (time_singleton.ticks - current_time_sync_start_ticks) / 2 + a_response.time else tmp_result := correction_factor * ((time_singleton.ticks - current_time_sync_start_ticks) /2 + a_response.time) + time * (1-correction_factor) end last_time := tmp_result.truncated_to_integer last_ticks := time_singleton.ticks debug ("EM_MP") io.put_string ("on successful sync response%N") end else debug ("EM_MP") io.put_string ("on failed sync response%N") end end end enable_time_sync is -- Enable time sync require a_time_sync_group_is_needed: time_sync_group /= Void not_already_enabled: not is_time_sync_enabled do is_time_sync_enabled := true volatile_period := 125 time_singleton.add_timed_procedure(time_sync_agent, 0) end disable_time_sync is -- Disable time sync require not_already_disabled: is_time_sync_enabled do is_time_sync_enabled := false end set_time_sync_group(a_group: EM_NET_GROUP) is -- set time_sync_group to a_group require a_group_not_void: a_group /= Void do time_sync_group:= a_group ensure time_sync_group_set: time_sync_group = a_group end set_is_time_master(a_value: BOOLEAN) is -- Set is_time_master to a_value do is_time_master := a_value ensure is_time_master_set: is_time_master = a_value end set_time_sync_frequency(a_period_in_ms: INTEGER) is --Sset time_sync_period to a_period_in_ms require a_positive_period: a_period_in_ms > 0 do time_sync_period := a_period_in_ms ensure time_sync_period_set: time_sync_period = a_period_in_ms end set_correction_factor(a_factor: DOUBLE) is -- set correction_factor to a_factor require a_factor_in_range: a_factor >=0 and a_factor <= 1 do correction_factor := a_factor ensure correction_factor_set: correction_factor = a_factor end time: INTEGER is -- Time in miliseconds local new_ticks: INTEGER do new_ticks := time_singleton.ticks Result:= new_ticks- last_ticks + last_time end time_sync_group: EM_NET_GROUP -- Group used for time synchronisation. is_time_master: BOOLEAN -- Is this a time master? -- In a client/server architecture you may elect the server to the time master. is_time_sync_enabled: BOOLEAN -- Is time synchronisation enabled? last_packet : EM_UDP_PACKET -- Last UDP packet that was created by `create_packet' time_sync_period: INTEGER -- How many miliseconds should be waited until the next sync request is sent? correction_factor: DOUBLE -- Correction factor -- How much weight do you want to give the new measured time compared to the old value? -- Use a reasonable value in general not too big. -- A value of 1 means that you well completely take the new measured time. -- A value of 0 means that you don't give any value to the new measured time. feature -- Element change set_object_update_handler (a_handler: PROCEDURE[ANY,TUPLE[INTEGER,INTEGER,EM_NET_UNSERIALIZER]]) is -- Set the handler that is called if updated data from a client arrived. -- Arguments that are passed to the event handler: -- time: INTEGER -- object ID: INTEGER -- serialized object: STRING do object_update_handler := a_handler end feature {NONE} -- Implementation object_update_handler: PROCEDURE[ANY,TUPLE[INTEGER,INTEGER,EM_NET_UNSERIALIZER]] -- Handler that is called if updated data from a client arrived message_prefix: STRING is "EM_MP" -- Prefix that is sent in front of each message -- Used as identifier version: INTEGER is 1 -- Version of the protocol -- Used to detect problems with incompatible protocol versions time_sync_agent: PROCEDURE [ANY, TUPLE] -- Time synchronisation agent -- Necessary to enable and disable timesynchonisation current_time_sync_request_id: INTEGER -- used to avoid errors current_time_sync_start_ticks: INTEGER -- start ticks last_time: INTEGER -- last time in hundreds of a second last_ticks: INTEGER -- last time in ms volatile_period: INTEGER -- time_sync_measurements: DS_LINKED_LIST[EM_PAIR[INTEGER,INTEGER]] invariant time_sync_enabled_needs_time_sync_group: is_time_sync_enabled implies (time_sync_group /= Void) end