Multithreading - Script Improvement (ie. Happy Fun Time)

Everything todo with programming goes HERE.
Post Reply
User avatar
Light
Reverse Outside Corner Grinder
Posts: 1667
Joined: Thu Oct 20, 2011 2:11 pm

Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Light »

Well, it randomly occurred to me while working on something completely irrelevant that if we made scripts in Java, we could implement multi-threading for things that "wait" and it wouldn't bother the script in the least.

I made a little proof-of-concept just to test out the idea, and it appears to work perfectly well. Java is a tad less helpful when dealing with strings, arrays, etc. than PHP is, but I think this could provide an improvement worth the extra work.

Oh, and Java also holds the platform independent title like PHP, and can (if you reallllllllly wanted, though I'd hope not) release scripts in closed source.

Here's my little test. It just spams someone with console messages. You type "/spam Kira 5 1 What's up?" and it will spam "Kira" 5 times, every 1 second with the message "What's up?". :) Of course, this is something you would probably never actually make for a server, but it shows the point, as you can have multiple people spamming eachother.

Off the top of my head, I can think of it being useful in servers like Happy Fun Time, where there's a delay between things happening. Rather than holding up the script for (not very long), you could throw it in another thread. It also helps with the delayed commands and whatnot. And you could use it for infinite loops until something happens, then myThread.stop(). Tons of things it could be used for. Kind'a a whole new section of ideas opened up for armagetron scripts .. at least for me.

So, here it is ...

MultiThreadSpam.java

Code: Select all

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class MultiThreadSpam
{
	public static String line;
	public static String[] split;
	
	public static Map<String, Thread> spamUsers = new HashMap<String, Thread>();
	public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	
	public static void main(String[] args)
	{
		while (getLine())
		{
			if (split[0].equals("INVALID_COMMAND"))
			{
				String sender = split[2];
				String command = split[1].substring(1);
				
				if (command.equalsIgnoreCase("spam") && split.length > 8)
				{
					if (!spamUsers.containsKey(sender) && isNumeric(split[6]) && isNumeric(split[7]))
					{
						int amount, delay;
						
						String user = split[5];
						amount = Integer.parseInt(split[6]);
						delay = Integer.parseInt(split[7]);
						String message = join(Arrays.copyOfRange(split, 8, split.length), " ");
						
						Spammer sendSpam = new Spammer(sender, user, message, amount, delay);
						Thread thread = new Thread(sendSpam);
						thread.start();
						
						spamUsers.put(sender, thread);
					}
				}
			}
		}
	}
	
	public static boolean getLine()
	{
		try
		{
			line = br.readLine();
		}
		catch (IOException io)
		{
			return false;
		}
		
		split = line.split("\\s+");
		return true;
	}
	
	public static String join(String[] words, String glue)
	{
		String text = "";
		
		for (String word : words)
		{
			text += text.length() > 0 ? " " + word : word;
		}
		
		return text;
	}
	
	public static boolean isNumeric(String input)
	{
		try
		{
			Integer.parseInt(input);
		}
		catch (Exception e)
		{
			return false;
		}
		
		return true;
	}
}
Spammer.java

Code: Select all

public class Spammer implements Runnable
{
	private static String sender;
	private static String user;
	private static String message;
	private static int times;
	private static int delay;
	
	public Spammer(String from, String to, String msg, int amount, int wait)
	{
		sender  = from;
		user    = to;
		message = msg;
		times   = amount;
		delay   = wait * 1000;
	}
	
	public void run()
	{
		for (int i = 0; i < times; i++)
		{
			System.out.println("PLAYER_MESSAGE " + user + " \"" + message + "\"");
			sleep(delay);
		}
		
		MultiThreadSpam.spamUsers.remove(sender);
	}
	
	public void sleep(int time)
	{
		try
		{
			Thread.sleep(time);
		}
		catch (InterruptedException e) {}
	}
}
I have put it up on my test server, and it works perfectly fine. I compiled it into a jar, and it's supported by the built-in script runner.

So, now I got a question. Do you guys see any downside to this? I mean, all I can see is benefits, minus the little bit of extra work. Java is a pretty easy language for people to learn as well that don't know how to script yet.

Curious of your thoughts.
epsy
Adjust Outside Corner Grinder
Posts: 2003
Joined: Tue Nov 07, 2006 6:02 pm
Location: paris
Contact:

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by epsy »

You don't need threading to do a sleep. Check out asynchronous programming and event loops. I don't really know the Java ecosystem, but Twisted is an example of such a framework for Python.
User avatar
Light
Reverse Outside Corner Grinder
Posts: 1667
Joined: Thu Oct 20, 2011 2:11 pm

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Light »

epsy wrote:You don't need threading to do a sleep. Check out asynchronous programming and event loops. I don't really know the Java ecosystem, but Twisted is an example of such a framework for Python.
Looking it up for Java links to threading. Maybe py would be a better language, but I don't know it at all really. I pretty much just work with what I can already do. :P But I never mind learning something new. I may check it out. I just thought this may be worth mentioning, since just about all scripts are written in PHP, except a couple I've seen in bash or perl, and I'm pretty sure it's not possible in any of them, without odd workarounds. I know it "can" be done, but not very nicely. Java has a pretty easy way of doing it I think.
epsy
Adjust Outside Corner Grinder
Posts: 2003
Joined: Tue Nov 07, 2006 6:02 pm
Location: paris
Contact:

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by epsy »

Here's an implementation of your script using Twisted, with no threading involved. I basically am telling the framework to do the waiting for me, while it can still do whatever in the meantime, like read stdin or spam other users. You might want to clamp your parameters though.

Code: Select all

from twisted.internet import reactor
from twisted.internet.stdio import StandardIO
from twisted.protocols.basic import LineReceiver

class AsyncSpammer(LineReceiver):
    """Accepts a "/spam user amount interval message" command"""

    from os import linesep as delimiter

    def escape_argument(self, arg):
        arg = arg.replace('\n', '\\n')
        if ' ' in arg:
            return '"{0}"'.format(arg.replace('"', '\\"'))
        else:
            return arg

    def send_command(self, *args):
        self.sendLine(' '.join(
            self.escape_argument(arg) for arg in args
            ))

    def lineReceived(self, line):
        args = line.split()
        lcmd = args.pop(0)

        if lcmd == "INVALID_COMMAND":
            command, sender = args[:2]
            if command.lower() == "/spam":
                try:
                    user, amount, interval = args[4:7]
                    amount = int(amount)
                    interval = float(interval)
                    message = ' '.join(args[7:])
                except ValueError:
                    message = ''
                if not message:
                    self.send_command(
                        'PLAYER_MESSAGE', sender,
                        "Darn, you can't even figure out how to use "
                        "the /spam command!"
                        )
                    return
                self.spam_user(user, amount, interval, message)

    def spam_user(self, user, amount, interval, message):
        self.send_command('PLAYER_MESSAGE', user, message)
        amount -= 1
        if amount > 0:
            reactor.callLater(
                interval,
                self.spam_user, user, amount, interval, message
                )

def main():
    StandardIO(AsyncSpammer())
    reactor.run()

if __name__ == '__main__':
    main()
User avatar
Light
Reverse Outside Corner Grinder
Posts: 1667
Joined: Thu Oct 20, 2011 2:11 pm

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Light »

How does that work without threading? If you don't run a separate thread, wouldn't the delayed command have to wait for the loop to hit a trigger again? Like setting a function called every so often to check if the time is to/after the trigger time, and execute. That's what it looks like to me anyway. I don't really get how else it would work without threading.

And if it does do something like the above, threading should give better precision. It should also provide you with a faster script.

It's got my attention. :P But, I don't think I'll be learning py any time soon.
epsy
Adjust Outside Corner Grinder
Posts: 2003
Joined: Tue Nov 07, 2006 6:02 pm
Location: paris
Contact:

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by epsy »

Light wrote:And if it does do something like the above, threading should give better precision. It should also provide you with a faster script.
It does something like the above, is more accurate than you think, while threading is not that fast at all. All of which are acceptably accurate/fast enough anyway when dealing with sending commands back and forth to/from Armagetron.

It requires discipline, but while the "easy" way out lets you use seemingly serialized code like you're used to, it gets ugly as soon as two threads want to modify and access the same memory at the same time. So it really isn't necessary when all involved is a bit of waiting, as opposed to computing digits of pi, which would actually block execution.

IIRC, Armagetronad only uses threading when resolving hostnames and contacting Global ID authorities, simply because the underlying system/library calls block(they just send their DNS or HTTP request and don't yield until they get a reply; ironically we don't really give a damn if we get the reply a moment later). All the in-game networking, reading/writing of streams(eg. to SPAWN_SCRIPT-started processes), timeouts, are handled by a mechanism similar to the above. Is Armagetronad too slow or imprecise?
User avatar
Light
Reverse Outside Corner Grinder
Posts: 1667
Joined: Thu Oct 20, 2011 2:11 pm

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Light »

I don't really have anything against it. That's how I do it in my PHP scripts when I need commands delayed. It just seemed like it would be more appropriate to do it in multiple threads. I see your point about them attempting to occur at the same time, so there are things you would need to watch out for, but for the most part you would probably only have a single thread dealing with writing to certain locations at any given time.

If they try to write to the same variable at the same time, or close to, would there be a problem? Seems like it would be unlikely, but I'm not quite sure.

I would imagine most of the time, they would simply output commands to the game. Throwing out a new thread for everything seems like it would be a bit excessive, but I can't see much of a downside to it for loops.

Now, I got a question. With your method, it seems as if it just gets stored in an array and waits. Now, if someone were to choose a ridiculous number, like a million messages be sent, then the script would yield while it adds them. Also, what would that do about memory usage?

I know that example is a bit exaggerated, and you would likely disallow it for this scenario, but it gets to my question well enough.
User avatar
Jonathan
A Brave Victim
Posts: 3391
Joined: Thu Feb 03, 2005 12:50 am
Location: Not really lurking anymore

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Jonathan »

Light wrote:If they try to write to the same variable at the same time, or close to, would there be a problem? Seems like it would be unlikely, but I'm not quite sure.
You will be in hell. Hell on Earth.
Light wrote:Now, I got a question. With your method, it seems as if it just gets stored in an array and waits. Now, if someone were to choose a ridiculous number, like a million messages be sent, then the script would yield while it adds them. Also, what would that do about memory usage?
You think an entry in a queue uses much space, whereas threads are free? Think again! Threads are heavyweight constructs. Things like sequential calls only need one entry, by the way, because they can add the next one themselves.
ˌɑrməˈɡɛˌtrɑn
User avatar
Light
Reverse Outside Corner Grinder
Posts: 1667
Joined: Thu Oct 20, 2011 2:11 pm

Re: Multithreading - Script Improvement (ie. Happy Fun Time)

Post by Light »

Jonathan wrote:You think an entry in a queue uses much space, whereas threads are free? Think again! Threads are heavyweight constructs. Things like sequential calls only need one entry, by the way, because they can add the next one themselves.
lol I know they're not free, but in comparison would have been by far better. Though, I didn't think of the obvious that you pointed out. :P
Post Reply