PistonDevelopers / hematite_server

A Minecraft server clone
http://hematite.piston.rs/
MIT License
140 stars 16 forks source link

Impl simple Scheduler #26

Open toqueteos opened 9 years ago

toqueteos commented 9 years ago

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 commented 9 years ago

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!

fenhl commented 9 years ago

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.

toqueteos commented 9 years ago

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?

fenhl commented 9 years ago

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.

RichardlL commented 8 years ago

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

fenhl commented 8 years ago

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

RichardlL commented 8 years ago

Oh yes, I was agreeing with you @fenhl

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

RichardlL commented 8 years ago

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

toqueteos commented 8 years ago

@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.

RichardlL commented 8 years ago

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.