-
Notifications
You must be signed in to change notification settings - Fork 24
Added timer-based & console-based announcement support #75
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
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,9 +26,62 @@ | |
| from other.utils import hexdump, server_base, server_main | ||
|
|
||
|
|
||
| def construct_broadcast(text="Hello, World!", text_color=0x00fffff0, | ||
| sender_id="000000", sender_name="[MH3SP]"): | ||
| assert len(text) > 0 | ||
| MAX_CHAR_LENGTH = 30 | ||
| broadcast_segments = [] | ||
| words = text.split(" ") | ||
|
|
||
| mod_words = [] | ||
|
|
||
| for word in words: | ||
| curr_word = word | ||
| while len(curr_word) > MAX_CHAR_LENGTH: | ||
| mod_words.append(curr_word[0:MAX_CHAR_LENGTH]) | ||
| curr_word = curr_word[MAX_CHAR_LENGTH:] | ||
| if len(curr_word) > 0: | ||
| mod_words.append(curr_word) | ||
|
|
||
| curr_broadcast = mod_words[0] | ||
| for word in mod_words[1:]: | ||
| if len(curr_broadcast) + 1 + len(word) <= MAX_CHAR_LENGTH: | ||
| curr_broadcast += " " | ||
| curr_broadcast += word | ||
| else: | ||
| broadcast_segments.append(curr_broadcast) | ||
| curr_broadcast = word | ||
| if len(curr_broadcast) > 0: | ||
| broadcast_segments.append(curr_broadcast) | ||
|
Comment on lines
+33
to
+55
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't the module |
||
|
|
||
| broadcasts = [] | ||
| for broadcast_text in broadcast_segments: | ||
| data = struct.pack(">B", 0x01) | ||
| info = pati.MessageInfo() | ||
| info.text_color = pati.Long(text_color) | ||
| info.sender_id = pati.String(sender_id) | ||
| info.sender_name = pati.String(sender_name) | ||
| data += info.pack() | ||
| data += pati.lp2_string(broadcast_text) | ||
| print("BROADCAST: " + broadcast_text) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we log this message properly, somewhere, rather than using raw prints?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the time it gets to the FmpServer, it's already bundled up into a binary. This function doesn't have access to any loggers since it's not local to any of the
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this print necessary since the server could log any packets including broadcast ones? |
||
| broadcasts.append((PatID4.NtcLayerChat, data, 0)) | ||
| return broadcasts | ||
|
|
||
|
|
||
| class FmpServer(PatServer): | ||
| """Basic FMP server class.""" | ||
| pass | ||
| def broadcast_message(self, text="Hello, World!"): | ||
| broadcast = construct_broadcast(text) | ||
| for broadcast_segment in broadcast: | ||
| self.broadcast_queue.put(broadcast_segment, block=True) | ||
|
|
||
| def event_check(self, timer): | ||
| # For time-based packet sending; to be implemented per-server | ||
| if timer.elapsed() >= 30 * 60: | ||
| self.broadcast_message("Hello, and thank you for playing on our \ | ||
| server! If you notice any bugs, please \ | ||
| report them in the Discord.") | ||
| timer.restart() | ||
|
|
||
|
|
||
| class FmpRequestHandler(PatRequestHandler): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,13 @@ | |
| import fmp_server as FMP | ||
| import rfp_server as RFP | ||
|
|
||
| try: | ||
| # Python 3 | ||
| import queue | ||
| except ImportError: | ||
| # Python 2 | ||
| import Queue as queue | ||
|
|
||
| from other.utils import create_server_from_base | ||
|
|
||
|
|
||
|
|
@@ -47,13 +54,21 @@ def main(args): | |
| """Master server main function.""" | ||
| servers, has_ui = create_servers(silent=args.silent, | ||
| debug_mode=args.debug_mode) | ||
| broadcast_queues = {server.__class__.__name__: queue.Queue() for server in servers} | ||
| threads = [ | ||
| threading.Thread(target=server.serve_forever) | ||
| threading.Thread(target=server.serve_forever, name=server.__class__.__name__, | ||
| args=(broadcast_queues[server.__class__.__name__], )) | ||
| for server in servers | ||
| ] | ||
| for thread in threads: | ||
| thread.start() | ||
|
|
||
| def broadcast_message(text): | ||
| # Function for easier broadcasting in interactive mode. | ||
| broadcast = FMP.construct_broadcast(text) | ||
| for broadcast_segment in broadcast: | ||
| broadcast_queues[FMP.BASE.cls.__name__].put(broadcast_segment, block=True) | ||
|
Comment on lines
+66
to
+70
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we iterate over the servers and call their |
||
|
|
||
| def interactive_mode(local=locals()): | ||
| """Run an interactive python interpreter in another thread.""" | ||
| import code | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -171,6 +171,7 @@ def __init__(self, server_address, RequestHandlerClass, max_threads, | |
| self.worker_threads = [] # type: List[threading.Thread] | ||
| self.worker_queues = [] # type: list[queue.queue] | ||
| self.selector = selectors.DefaultSelector() | ||
| #self.broadcast_queue = queue.Queue() # type: queue.Queue[(packet_id, data, seq)] | ||
|
|
||
| if max_threads <= 0: | ||
| max_threads = multiprocessing.cpu_count() | ||
|
|
@@ -206,19 +207,24 @@ def fileno(self): | |
| """ | ||
| return self.socket.fileno() | ||
|
|
||
| def serve_forever(self): | ||
| def serve_forever(self, broadcast_queue): | ||
| self.broadcast_queue = broadcast_queue | ||
|
Comment on lines
+210
to
+211
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing the |
||
| self.__is_shut_down.clear() | ||
| try: | ||
| with self.selector as selector: | ||
| selector.register(self, selectors.EVENT_READ) | ||
|
|
||
| event_watch = Timer() | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will do for the time being but a clock should be used. Having a single timer prevent handling multiple timed events. |
||
| write_watch = Timer() | ||
| write_timeout = 1 # Seconds | ||
| while not self.__shutdown_request: | ||
| ready = selector.select(write_timeout) | ||
| if self.__shutdown_request: | ||
| break | ||
|
|
||
| broadcast_packet = None | ||
| if not self.broadcast_queue.empty(): | ||
| broadcast_packet = self.broadcast_queue.get(block=True) | ||
| for (key, event) in ready: | ||
| selected = key.fileobj | ||
| if selected == self: | ||
|
|
@@ -237,6 +243,10 @@ def serve_forever(self): | |
| selected.on_exception(e) | ||
| if selected.is_finished(): | ||
| self.remove_handler(selected) | ||
| if broadcast_packet is not None: | ||
| for handler in self.handlers: | ||
| self._queue_work(handler, | ||
| broadcast_packet, selectors.EVENT_WRITE) | ||
| if write_watch.elapsed() >= write_timeout: | ||
| try: | ||
| for handler in self.handlers: | ||
|
|
@@ -249,6 +259,7 @@ def serve_forever(self): | |
| self.remove_handler(handler) | ||
| finally: | ||
| write_watch.restart() | ||
| self.event_check(event_watch) | ||
| finally: | ||
| self.__is_shut_down.set() | ||
|
|
||
|
|
@@ -266,13 +277,20 @@ def _worker_target(self, work_queue): | |
|
|
||
| if handler.is_finished(): | ||
| continue | ||
|
|
||
| assert event == selectors.EVENT_READ | ||
|
|
||
| try: | ||
| handler.on_packet(packet) | ||
| except Exception as e: | ||
| handler.on_exception(e) | ||
|
|
||
| if event == selectors.EVENT_WRITE: | ||
| try: | ||
| if handler.session.layer > 0: # in-game only | ||
| handler.send_packet(*packet) | ||
| except Exception as e: | ||
| handler.on_exception(e) | ||
| else: | ||
| assert event == selectors.EVENT_READ | ||
|
|
||
| try: | ||
| handler.on_packet(packet) | ||
| except Exception as e: | ||
| handler.on_exception(e) | ||
|
|
||
| if handler.is_finished(): | ||
| self.remove_handler(handler) | ||
|
|
@@ -307,6 +325,10 @@ def _queue_work(self, handler, work_data, event): | |
| thread_queue = self.worker_queues[handler.__worker_thread] | ||
| thread_queue.put((handler, work_data, event), block=True) | ||
|
|
||
| def event_check(self, timer): | ||
| # For time-based packet sending; to be implemented per-server | ||
| pass | ||
|
|
||
| def remove_handler(self, handler): | ||
| # type: (BasicPatHandler) -> None | ||
| try: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert textshould work.