Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 54 additions & 1 deletion fmp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

assert text should work.

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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can't the module textwrap and textwrap.wrap be used instead?


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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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 PatServer classes.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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):
Expand Down
17 changes: 16 additions & 1 deletion master_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can't we iterate over the servers and call their broadcast_message method rather than "hijacking" their broadcast_queues?


def interactive_mode(local=locals()):
"""Run an interactive python interpreter in another thread."""
import code
Expand Down
2 changes: 1 addition & 1 deletion mh/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def slot(item, qty):
return data


LAYER_CHAT_COLORS = (0xbb3385ff, 0xffffffff, 0xffffffff)
LAYER_CHAT_COLORS = (0xbb3385ff, 0xf0f0f0ff, 0xf0f0f0ff)

TERMS_VERSION = 1
TERMS = {
Expand Down
38 changes: 30 additions & 8 deletions mh/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Passing the broadcast_queue as a parameter seems overkill. Can't it be created in the server's constructor method?

self.__is_shut_down.clear()
try:
with self.selector as selector:
selector.register(self, selectors.EVENT_READ)

event_watch = Timer()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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:
Expand All @@ -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:
Expand All @@ -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()

Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down