Skip to content
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

Impl simple Scheduler #26

Open
toqueteos opened this issue Mar 3, 2015 · 10 comments
Open

Impl simple Scheduler #26

toqueteos opened this issue Mar 3, 2015 · 10 comments

Comments

@toqueteos
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Member

fenhl commented Mar 3, 2015

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
Copy link
Contributor Author

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
Copy link
Member

fenhl commented Mar 3, 2015

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.

@toqueteos toqueteos added this to the MC 1.8.3 milestone Mar 7, 2015
@toqueteos toqueteos changed the title Simple Scheduler Impl simple Scheduler Mar 7, 2015
@RichardlL
Copy link

@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
Copy link
Member

fenhl commented Dec 7, 2015

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

@RichardlL
Copy link

Oh yes, I was agreeing with you @fenhl

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

@RichardlL
Copy link

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

@toqueteos
Copy link
Contributor Author

@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
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants