planetkit 0.0.1

High-level toolkit for building games based around voxel globes.
Documentation
use na;
use specs;
use specs::{WriteStorage, Fetch};
use specs::Entities;
use slog::Logger;

use types::*;
use globe::{Globe, View, ChunkView};
use render::{Visual, ProtoMesh, Vertex};
use Spatial;

// For now, just creates up to 1 chunk view per tick,
// until we have created views for all chunks.
pub struct ChunkViewSystem {
    log: Logger,
    seconds_between_geometry_creation: TimeDelta,
    seconds_since_last_geometry_creation: TimeDelta,
}

impl ChunkViewSystem {
    pub fn new(
        parent_log: &Logger,
        seconds_between_geometry_creation: TimeDelta,
    ) -> ChunkViewSystem {
        ChunkViewSystem {
            log: parent_log.new(o!()),
            seconds_between_geometry_creation: seconds_between_geometry_creation,
            seconds_since_last_geometry_creation: 0.0,
        }
    }

    fn build_chunk_geometry<'a>(
        &mut self,
        mut globes: specs::WriteStorage<'a, Globe>,
        mut visuals: specs::WriteStorage<'a, Visual>,
        // TODO: Parameterise over ReadStorage/WriteStorage when we don't care?
        // TODO: I made `MaybeMutStorage` for `SpatialStorage`, so just pluck
        // that out somewhere public and use that.
        chunk_views: specs::WriteStorage<'a, ChunkView>,
    ) {
        // Throttle rate of geometry creation.
        // We don't want to spend too much doing this.
        let ready = self.seconds_since_last_geometry_creation >
            self.seconds_between_geometry_creation;
        if !ready {
            return;
        }

        use specs::Join;
        for (visual, chunk_view) in (&mut visuals, &chunk_views).join() {
            // TODO: find the closest mesh to the player that needs
            // to be generated (i.e. absent or dirty).
            //
            // TODO: eventually, some rules about capping how many you create.

            // Get the associated globe, complaining loudly if we fail.
            let globe_entity = chunk_view.globe_entity;
            let globe = match globes.get_mut(globe_entity) {
                Some(globe) => globe,
                None => {
                    warn!(
                        self.log,
                        "The globe associated with this ChunkView is not alive! Can't proceed!"
                    );
                    continue;
                }
            };

            // Only re-generate geometry if the chunk is marked as having
            // been changed since last time the view was updated.
            //
            // Note that this will also be true if geometry has never been created for this chunk.
            //
            // TODO: this might also need to be done any time any of its neighboring
            // chunks changes, because we cull invisible cells, and what cells are
            // visible partly depends on what's in neighboring chunks.
            use globe::globe::GlobeGuts;
            let spec = globe.spec();
            {
                // Ew, can I please have non-lexical borrow scopes?
                let chunk = &mut globe.chunks_mut().get(&chunk_view.origin)
                    .expect("Don't know how to deal with chunk not loaded yet. Why do we have a view for it anyway?");
                if !chunk.is_view_dirty {
                    continue;
                }
            }

            // Make a proto-mesh for the chunk.
            // TODO: debounce/throttle per chunk.
            trace!(self.log, "Making chunk proto-mesh"; "origin" => format!("{:?}", chunk_view.origin));
            // TEMP: just use the existing globe `View` struct
            // to get this done. TODO: move into `ChunkView`.
            let globe_view = View::new(spec, &self.log);
            // Build geometry for this chunk into vertex
            // and index buffers.
            let mut vertex_data: Vec<Vertex> = Vec::new();
            let mut index_data: Vec<u32> = Vec::new();
            globe_view.make_chunk_geometry(
                globe,
                chunk_view.origin,
                &mut vertex_data,
                &mut index_data,
            );

            // Mark the chunk as having a clean view.
            // NOTE: we need to do this before maybe skipping
            // actually building the view.
            {
                // Ew, can I please have non-lexical borrow scopes?
                let chunk = &mut globe.chunks_mut().get_mut(&chunk_view.origin)
                    .expect("Don't know how to deal with chunk not loaded yet. Why do we have a view for it anyway?");
                chunk.mark_view_as_clean();
            }

            // Don't attempt to create an empty mesh.
            // Back-end doesn't seem to like this, and there's no point
            // in wasting the VBOs etc. for nothing.
            if vertex_data.is_empty() || index_data.is_empty() {
                trace!(self.log, "Skipping chunk proto-mesh that would be empty"; "origin" => format!("{:?}", chunk_view.origin));

                // TODO: is there anything that will assume we need to make the
                // mesh again just because there's no mesh for the view?
                // Maybe we need to make the case of an empty `Visual` explicit
                // in that type to avoid mistakes.
                continue;
            }

            visual.proto_mesh = ProtoMesh::new(vertex_data, index_data).into();

            trace!(self.log, "Made chunk proto-mesh"; "origin" => format!("{:?}", chunk_view.origin));

            // Do at most 1 per frame; probably far less.
            self.seconds_since_last_geometry_creation = 0.0;
            return;
        }
    }

    pub fn remove_views_for_dead_chunks<'a>(
        &mut self,
        entities: &Entities<'a>,
        globe: &mut Globe,
        globe_entity: specs::Entity,
        visuals: &mut specs::WriteStorage<'a, Visual>,
        chunk_views: &mut specs::WriteStorage<'a, ChunkView>,
    ) {
        use specs::Join;

        let mut entities_to_remove: Vec<specs::Entity> = Vec::new();

        for (chunk_view, chunk_view_ent) in (&*chunk_views, &**entities).join() {
            // Ignore chunks not belonging to this globe.
            if chunk_view.globe_entity != globe_entity {
                continue;
            }

            if globe.chunk_at(chunk_view.origin).is_none() {
                debug!(self.log, "Removing a chunk view"; "origin" => format!("{:?}", chunk_view.origin));
                entities_to_remove.push(chunk_view_ent);
            }
        }

        for chunk_view_ent in entities_to_remove {
            // TODO: don't forget to remove the MESH.
            // TODO: don't forget to remove the MESH.
            // TODO: don't forget to remove the MESH.
            // TODO: don't forget to remove the MESH.
            // TODO: don't forget to remove the MESH.
            //
            // If you don't do that, then we'll slowly leak VBOs etc.
            //
            // But you can get away with not doing that for now because
            // the tests don't start the render system, and so will never
            // make those meshes to begin with.

            // Remove Visual and ChunkView components (to prevent accidentally
            // iterating over them later within the same frame) and then queue
            // the entity itself up for deletion.
            visuals.remove(chunk_view_ent);
            chunk_views.remove(chunk_view_ent);
            entities.delete(chunk_view_ent).expect("Somehow tried to use an entity with the wrong generation!");

            // TODO: maintain? Do we need to do that here?
            // Or are these entities immediately inaccessible?
            // (I'm unclear on when it's necessary / exactly what it does.)

        }
    }

    pub fn ensure_chunk_view_entities<'a>(
        &mut self,
        entities: &Entities<'a>,
        globe: &mut Globe,
        globe_entity: specs::Entity,
        chunk_views: &mut specs::WriteStorage<'a, ChunkView>,
        visuals: &mut specs::WriteStorage<'a, Visual>,
        spatials: &mut specs::WriteStorage<'a, Spatial>,
    ) {
        use globe::globe::GlobeGuts;
        let globe_spec = globe.spec();
        for chunk in globe.chunks_mut().values_mut() {
            if chunk.view_entity.is_some() {
                continue;
            }
            trace!(self.log, "Making a chunk view"; "origin" => format!("{:?}", chunk.origin));
            let chunk_view = ChunkView::new(globe_entity, chunk.origin);

            // We store the geometry relative to the bottom-center of the chunk origin cell.
            let chunk_origin_pos = globe_spec.cell_bottom_center(*chunk.origin.pos());
            let chunk_transform = Iso3::new(chunk_origin_pos.coords, na::zero());

            // We'll fill it in later.
            let empty_visual = ::render::Visual::new_empty();
            // TODO: Use `create_later_build`, now that it exists?
            // Updated TODO: figure out what the new API is for that.
            let new_ent = entities.create();
            // TODO: run `maintain`? When do we have to do that?
            chunk.view_entity = Some(new_ent);
            chunk_views.insert(new_ent, chunk_view);
            visuals.insert(new_ent, empty_visual);
            spatials.insert(new_ent, Spatial::new(globe_entity, chunk_transform));
        }
    }
}

impl<'a> specs::System<'a> for ChunkViewSystem {
    type SystemData = (Entities<'a>,
     Fetch<'a, TimeDeltaResource>,
     WriteStorage<'a, Globe>,
     WriteStorage<'a, Visual>,
     WriteStorage<'a, Spatial>,
     WriteStorage<'a, ChunkView>);

    fn run(&mut self, data: Self::SystemData) {
        use specs::Join;
        let (entities, dt, mut globes, mut visuals, mut spatials, mut chunk_views) = data;

        self.seconds_since_last_geometry_creation += dt.0;

        // Destroy views for any chunks that are no longer loaded.
        for (globe, globe_entity) in (&mut globes, &*entities).join() {
            self.remove_views_for_dead_chunks(
                &entities,
                globe,
                globe_entity,
                &mut visuals,
                &mut chunk_views,
            );

            // Ensure that there is a visual for
            // every chunk currently loaded in the globe.
            //
            // TODO: we don't actually want to do this
            // long-term; it's just a first step in migrating
            // to systems-based view creation. Eventually we'll
            // be selective about what views to have; i.e. we might
            // have 1000 chunks loaded, and only render 200 of them
            // on this client.
            self.ensure_chunk_view_entities(
                &entities,
                globe,
                globe_entity,
                &mut chunk_views,
                &mut visuals,
                &mut spatials,
            );
        }

        // Build geometry for some chunks; throttled
        // so we don't spend too much time doing this each frame.
        self.build_chunk_geometry(globes, visuals, chunk_views);
    }
}