hematite_server icon indicating copy to clipboard operation
hematite_server copied to clipboard

Impl simple Scheduler

Open toqueteos opened this issue 9 years ago • 10 comments

Since packets are going through an awesome refactor I'm doing other things meanwhile.

I would like some opinions on this simple scheduler approach, it's almost working, can't get closure call to work properly, also I had to improvise the Server trait.

I first tried with <F: FnMut(S), S: Server> but it gave too many headaches.

Usages: keep alive packet (one for every player every 20~30s), time update packet (one for every player every second, 20 ticks), update health (update health packet), ...

use std::marker::PhantomData;

use time::{self, Timespec};

pub trait Server {
    pub fn compression(&self) -> bool;
    pub fn set_compression(&self, bool);
}

pub trait Scheduler<F> where F: FnMut(&Server) {
    fn schedule(&self, when: Timespec, action: F);
    fn update(&self);
}

pub struct Event<F: FnMut(&Server)> {
    pub when: Timespec,
    pub action: F,
    // _phantom: PhantomData<S>
}

pub struct ServerScheduler<F: FnMut(&Server), S: Server> {
    pub events: Vec<Event<F>>,
    // pub events: Vec<(Timespec, F)>,
    pub server: S
}

impl<F, S> ServerScheduler<F, S>
    where F: FnMut(&Server), S: Server {
    fn new(server: S) -> ServerScheduler<F, S> {
        ServerScheduler {
            events: Vec::new(),
            server: server
        }
    }
}

impl<F, S> Scheduler<F> for ServerScheduler<F, S>
    where F: FnMut(&Server), S: Server {
    fn schedule(&self, when: Timespec, action: F) {
        self.events.push(Event{when: when, action: action});
    }

    fn update(&self) {
        let start = time::now().to_timespec();
        let remove = Vec::new();
        for (idx, evt) in self.events.iter().enumerate() {
            if evt.when <= start {
                let f = evt.action;
                f(self.server);
                remove.push(idx);
            }
        }
        let mut i = 0;
        for elt in remove.into_iter() {
            self.events.remove(elt - i);
            i += 1;
        }
    }
}

toqueteos avatar Mar 03 '15 00:03 toqueteos

A queue or priority queue might be a better aproach instead of callbacks.

Timer could sit on main thread and callbacks are ran in a pool of threads (excluding main). Callbacks or queue have both timing issues. Game servers, yay!

toqueteos avatar Mar 03 '15 01:03 toqueteos

Here's a different approach:

Each client connection is managed by a thread, which handles packet encoding/decoding (this type of io should be async), and we use timers to push scheduled packets into its input queue. This works well because queues are multi-producer.

Things like time update which are tied to ticks should be produced by the central game loop.

fenhl avatar Mar 03 '15 04:03 fenhl

I'm all for a 1-client-1-thread approach but will it scale properly with Rust's native threads? Green threads are awesome for this, how's the support for them in Rust?

toqueteos avatar Mar 03 '15 11:03 toqueteos

Currently? Completely absent, as far as I'm aware. However, we could use native threads for now and switch to green threads when they become usable.

fenhl avatar Mar 03 '15 12:03 fenhl

@toqueteos @fenhl

Most large Servers are hosting on Linux these days, where threads are cheap (also on OSX).

Single player on Windows wouldn't matter, as 1:1 for one person is still 1

Some small servers (such as just a group of friends) would be okay on windows too, as most computers these days have 4+ cores, even laptops.

Technically unnecessary?

Any thoughts?

RichardlL avatar Dec 06 '15 22:12 RichardlL

@RichardlL I maintain that we should use one thread per connection, as outlined above. That would make this unnecessary, right?

fenhl avatar Dec 07 '15 07:12 fenhl

Oh yes, I was agreeing with you @fenhl

That's what my plan was. I was just saying there is no overhead concern.

RichardlL avatar Dec 07 '15 14:12 RichardlL

Why does the update health packet need to be on a loop, why not just send it whenever it changes? @toqueteos

RichardlL avatar Dec 18 '15 02:12 RichardlL

@RichardlL I actually don't remember! There's no mentions about that on http://wiki.vg/Protocol#Update_Health so maybe it's a typo or some missunderstanding from my side.

toqueteos avatar Dec 19 '15 00:12 toqueteos

I propose we implement this using channels and a sleep thread.

Instead of having timed things updated on a tick loop, we could manage it with another thread held by a channel.

The timeout of the channel will be set to whatever the next event to happens, for example, plant growth.

If it receives a new event, it checks it for a timeout shorter than the the current soonest event. If it is, use that, else we would add it to the Que.

I believe, in vanilla, the tick loop is ~20-50% of time the game loop; this might show a good bit of benefit :)

The keep-alive thread could also manage the time updates.

RichardlL avatar Dec 19 '15 03:12 RichardlL