我很难做到这一点,每一次try ,我都遇到了一些我无法解决的问题.

对于上下文:

我正在构建一个实体组件系统,其中的组件在Anymap中打包成array.我所说的压缩数组是一种数据 struct ,它看起来像一个有许多空槽的数组,索引是实体id:例如,索引5处的组件是附加到id 5的实体的组件. 由于并非所有实体都具有所有组件,因此存在大量空槽,因此压缩数组是IndexedElem<T>Vec,以保持内存中数据的紧密性:

pub struct PackedArray<T> {
    data: Vec<IndexedElem<T>>,
}

pub struct IndexedElem<T> {
    pub index: usize,
    pub elem: T
}

现在,组件表本身就是这PackedArray个表中的AnyMap个:

pub struct ComponentTable {
    components: anymap::Map,
}

所以我已经掌握了所有的基础知识,比如创建实体、添加/删除/获取组件.

但现在,我希望能够迭代组件(这就是将组件保存在压缩数组表中的全部意义).

迭代1个组件很容易,我只需迭代Vec个组件.我正在苦苦挣扎的地方是迭代几个组件:

假设我想迭代所有的组件对C1和C2(意味着同时具有C1和C2的所有实体)

我们的 idea 是得到这两个压缩数组的迭代器,然后我有一个从0开始的current_entity_id,我判断两个迭代器的IndexedElem是否与id相同,如果是,则返回两个元素,如果不是,则转到下一个元素.

理论上,我现在将如何建造它,但我在Rust 的时候很难实现它,有生命,有所有权,等等……

这是我最后一次try :

我有一个ComponentIterator_2 struct ,实现类型为(C1, C2)的迭代器特征:

pub struct ComponentIterator_2<'a, C1, C2> {
    iterator_1: std::slice::IterMut<'a, IndexedElem<C1>>,
    iterator_2: std::slice::IterMut<'a, IndexedElem<C2>>,
    current_entity: usize,
}

但当我试着这样建造它时:

    pub fn iterate_over_2_component<'a, C1: 'static, C2: 'static>(&'a mut self) -> Option<ComponentIterator_2<'a, C1, C2>> {
        return Some(ComponentIterator_2::<C1, C2> {
            iterator_1: match self.components.get_mut::<PackedArray<C1>>() {
                None => return None,
                Some(packed_array) => packed_array.iter_mut(),
            },
            iterator_2: match self.components.get_mut::<PackedArray<C2>>() {
                None => return None,
                Some(packed_array) => packed_array.iter_mut(),
            },
            current_entity: 0,
        });
    }

在这里,我不能借两次self.components英镑,我明白这一点,但我绕不开它.

我试过很多其他的方法,但我已经挣扎到现在在这里寻求帮助了!

因此,如果有人能给我一些关于ho的提示,以便正确地实现这一点,如果可能的话,以一种有效的方式,我将非常高兴.

整个项目(ECS,周围没有其他东西)在my GitHub上可用.

推荐答案

经过多次试错,我终于做到了!

按照建议,我已经添加了ComponentArray,这是UnsafeCell<PackedArray<C>>的包装.这允许从组件表borrow 多个向量,然后跟踪这些向量中的每个索引以迭代它们.

我甚至把这向前推进了一步,因为我已经实现了一个宏,它允许迭代任意数量的组件.这就是它(在这一点上有点胡说八道)


#[macro_export]
macro_rules! fn_internal_get_next_elem {
    ($elem_type:ty; $first:path, $($elems:path),*) => {
        fn macro_generated_return_next(elem: $elem_type) -> $elem_type {
            match elem {
                $first => 
                $($elems, $elems =>)*
                $first
            }
        }

        fn macro_generated_reset() -> $elem_type {
            $first
        }
    }
}

#[macro_export]
macro_rules! iterate_over_component {
    ($ecs:expr; $($comp:ident),+) => {
        {
            // use statments to import everything that is needed
            use crate::utils::collections::packed_array::IndexedElem;
            use crate::ecs::component_array::ComponentArray;

            // use an enum to get an id per component !
            #[derive(Copy, Clone)]
            enum MacroGeneratedComponentsEnum {
                $(
                    $comp,
                )+
                EndOfIterator
            }

            // generate methods to go to next components enum
            fn_internal_get_next_elem!(MacroGeneratedComponentsEnum; $(MacroGeneratedComponentsEnum::$comp, )+ MacroGeneratedComponentsEnum::EndOfIterator);

            // struct to pack both a vec and a index
            struct MacroGeneratedIterableVec<'a, T> {
                vec: Option<&'a Vec<IndexedElem<T>>>,
                index: usize,
            }
            // create the result struct that will act as an iterator
            struct MacroGeneratedComponentIterator<'a, $($comp),+> {
                current_iterator: MacroGeneratedComponentsEnum,
                current_entity: usize,
                $(
                    $comp: MacroGeneratedIterableVec<'a, $comp>,
                )+
            }

            // implement the iterator 
            impl<'a, $($comp),+> Iterator for MacroGeneratedComponentIterator<'a, $($comp),+> {
                type Item = ($(&'a $comp),+);
                fn next(&mut self) -> Option<Self::Item> {
                    // a bit tricky but allows static enums as values :
                    // create usize vars of name comp that store their enums values
                    $(
                        let $comp: usize = MacroGeneratedComponentsEnum::$comp as usize;
                    )+
                    loop {
                        match self.current_iterator {
                            $(
                                MacroGeneratedComponentsEnum::$comp => {
                                    // checking for first component
                                    while match self.$comp.vec {
                                        None => return None,
                                        Some(array) => {
                                            match array.get(self.$comp.index) {
                                                None => return None, // out of element on first vec, end of iterator
                                                Some(i_elem) => {
                                                    // use this to update values
                                                    if i_elem.index < self.current_entity {
                                                        // true to keep the while loop and increment index
                                                        true
                                                    }
                                                    else {
                                                        // if we are bigger than current entity, update entity to match ourselves
                                                        if i_elem.index > self.current_entity {
                                                            // update entity to align to our component
                                                            self.current_entity = i_elem.index;
                                                            // reset current iterator because we went to next entity, so need to get again all components
                                                            self.current_iterator = macro_generated_reset();
                                                            // note that the while loop will end, so the loop will come back to this point
                                                            // except it will then go to the else and increment the current iterator
                                                            // this is a design choice so this code is similar in every arm of the match on self.current_iterator
                                                        }
                                                        else {
                                                            // check next iterator, we are at the component of current entity
                                                            self.current_iterator = macro_generated_return_next(self.current_iterator);
                                                        }
                                                        false // go to next iterator, so end while loop
                                                    }
                                                },
                                            }
                                        }
                                    } {
                                        // advance current index of array 1 to match with current entity
                                        self.$comp.index += 1;
                                    }
                                }
                            )+
                            _ =>{
                                                    // here, all arrays index have matched the entity, so let's return the components !
                                let result = Some((
                                    $(
                                        match self.$comp.vec {
                                            None => return None, // shouldn't happen, but safety
                                            Some(array) => match array.get(self.$comp.index) {
                                                None => return None, // shouldn't happen, but safety
                                                Some(i_elem) => &i_elem.elem,
                                            }
                                        }
                                    ),+
                                ));
                                // update to next entity for iterator
                                self.current_entity += 1;
                                // reset iterator counter
                                self.current_iterator = macro_generated_reset();
                            
                                return result;
                            }
                        }
                    }
                }
            }

            // get the comp map once to avoid multi borrow issues (we have unsafe cell for vectors)
            let mut comp_map = ECS::get_unsafe_component_map($ecs);

            MacroGeneratedComponentIterator::<$($comp),+> {
                current_iterator: macro_generated_reset(),
                current_entity: 0,
                $(
                    $comp: MacroGeneratedIterableVec {
                        vec: match comp_map.get::<ComponentArray<$comp>>() {
                            None => None,
                            Some(comp_arr) => Some(comp_arr.get_array()),
                        },
                        index: 0,
                    },
                )+
            }
        }
    };
}

不管怎么说,它运行得很好!整个项目的代码是on my git可用的.

Rust相关问答推荐

Rust为什么应用于引用的操作符可以强制,而具有显式类型的let则不能?

为什么父作用域中的变量超出了子作用域

为什么我们不能通过指针算法将Rust原始指针指向任意地址?'

Rust中的相互递归特性与默认实现

在Rust中,有没有一种方法让我定义两个 struct ,其中两个都遵循标准 struct ?

何时可以在Rust中退出异步操作?

Rust类似功能是C++命名空间吗?

如何将像烫手山芋一样不透明的值从一个Enum构造函数移动到下一个构造函数?

闭包不会发送,即使它只捕获发送变量

替换可变引用中的字符串会泄漏内存吗?

将PathBuf转换为字符串

有没有可能让泛型Rust T总是堆分配的?

这是什么:`impl Trait for T {}`?

借来的价值生命周期 不够长,不确定为什么它仍然是借来的

为什么某些类型参数仅在特征边界中使用的代码在没有 PhantomData 的情况下进行编译?

方法可以被误认为是标准特性方法

Rust 编译器不统一在 if let 表达式的分支中都 impl Future 的类型

borrow 匹配手臂内部的可变

为什么这个闭包没有比 var 长寿?

如何从 many0 传播 Nom 失败上下文?