简体   繁体   English

在C ++中存储和检索复杂对象的有效方法?

[英]Efficient way of storing and retrieving complex objects in C++?

I'm currently working on a small dungeon simulation game. 我正在制作一个小型地下城模拟游戏。 The game is quite detailed and i'm planning to have, over time, +200k instances of a class that represents a "monster". 游戏非常详细,我计划随着时间的推移,有一个代表“怪物”的类的+ 200k实例。 They contain perks, skills and history of that monster. 它们包含该怪物的特权,技能和历史。 Things like how many potions he has used, where he lives, what are his patrol routes, etc. 像他使用了多少药水,他住在哪里,他的巡逻路线是什么等等。

I started implementing this with SQLite and using a simple table called "monsters", which contained all the data. 我开始用SQLite实现它,并使用一个名为“monsters”的简单表,其中包含所有数据。 This allowed me to use SQL queries to find the monsters needed for simulation calculation on each frame. 这允许我使用SQL查询来查找每帧上模拟计算所需的怪物。 For example: finding all monsters who patrolled point A, or finding all monsters who used Potion X, etc. Unfortunately, querying SQLite several times on every frame quickly slowed down the game. 例如:找到所有巡逻A点的怪物,或找到使用了药水X的所有怪物等等。不幸的是,在每一帧上多次查询SQLite会很快减慢游戏速度。 Even though it's a 2D game, i need the precious milliseconds for simulation calculations. 即使它是2D游戏,我也需要宝贵的毫秒来进行模拟计算。

Also, i was going to need JOIN's in the future to do graphs: i need to know if a monster has attacked another monster or if a monster is part of the team of another monster. 此外,我将来需要JOIN来制作图表:我需要知道怪物是否攻击了另一个怪物,或者怪物是否是另一个怪物团队的一部分。 That would slow things even further. 这会使事情进一步减缓。

Does anyone have any suggestion on how to approach this? 有没有人对如何处理这个有任何建议?

My data resembles something like this: 我的数据类似于以下内容:

http://farm3.static.flickr.com/2450/3614560130_aaafa37387.jpg?v=0

If you are not using an entity component system, consider migrating your game to using one. 如果您不使用实体组件系统,请考虑将游戏迁移到使用一个。 Each bit of related data can be stored in a self-contained component, and the entity itself is some opaque identifier that identifies the component. 每个相关数据位可以存储在一个独立的组件中,实体本身是一个标识该组件的不透明标识符。 When using an ECS, rather than have a game entity with a bunch of data hanging off it, you reverse the relationship. 使用ECS时,不要让游戏实体挂起一堆数据,而是颠倒关系。 All components of a specific type exist in a big pool together, and your entity is just an identifier that specifies which component in this pool they care about. 特定类型的所有组件一起存在于一个大池中,并且您的实体只是一个标识符,用于指定它们关注的池中的哪个组件。 What this allows you to do is batch component updates. 这允许您做的是批量组件更新。 If you have an Inventory component on each monster with an inventory, all of your Inventory components can be stored more or less contiguously in memory. 如果每个具有库存的怪物都有一个库存组件,则所有库存组件可以或多或少地连续存储在内存中。 This means that when processing them, you have high cache coherency which can be a significant performance boost. 这意味着在处理它们时,您具有高缓存一致性,这可以显着提升性能。

It also might be that you're just trying to do too much each frame. 也可能是你只想尝试每帧做太多。 With an entity component system you can throttle specific subsystems to every X frames or every X seconds if you need to. 使用实体组件系统,您可以根据需要将特定子系统限制为每X帧或每X秒。 Maybe the AI component only needs to run once a second to think about what to do next, but they need to keep moving continuously so you update position every frame. 也许AI组件只需要每秒运行一次以考虑下一步该做什么,但是它们需要不断移动以便每帧都更新位置。 Or maybe putting together one of the charts and graphs takes too long to complete in one frame so you have it calculated every second frame, or split the processing over two frames so that you iterate over half your entities on frame and the rest on the second frame. 或者将图表和图形中的一个放在一起需要花费很长时间才能在一个帧中完成,因此您可以每隔一帧计算一次,或者将处理分成两帧,以便在帧上迭代一半以上的实体,其余的在第二帧上迭代帧。 There's a lot of ways you can split it up. 有很多方法可以拆分它。

Here is some more reading on component systems if you haven't seen them before 如果您以前没有看过它们,可以在组件系统上阅读更多内容

As I understood, the main problem for you is find optimized way to store your monsters. 据我所知,你遇到的主要问题是找到存储怪物的优化方法。 For example, you can use some tree data structures to find effectively needed monsters on plane. 例如,您可以使用一些树数据结构在平面上查找有效的所需怪物。 One of these data structures is BSP (binary search partition) tree. 这些数据结构之一是BSP(二进制搜索分区)树。 It is brief description https://en.wikipedia.org/wiki/Binary_space_partitioning . 简要说明https://en.wikipedia.org/wiki/Binary_space_partitioning Qt's graphic view framework uses this approach. Qt的图形视图框架使用这种方法。 For more information about it you can see at http://doc.qt.io/qt-4.8/graphicsview.html 有关它的更多信息,请参阅http://doc.qt.io/qt-4.8/graphicsview.html

There hasn't been an accepted answer, so I'll give your question a shot. 没有一个公认的答案,所以我会给你一个问题。 But I'll be honest with you ;-) 但我会跟你说实话;-)

First: You did not specify too many details, this makes a really well fitting answer difficult, if not impossible. 第一:你没有指定太多的细节,这使得一个非常合适的答案很难,如果不是不可能的话。 For me, it's not clear how much data you want to process in what time. 对我来说,目前尚不清楚您希望在什么时间处理多少数据。 You mention frames, but is this a frame like in frames per second or is it more like what I'd call "a world tick"? 你提到帧,但这是一帧如每秒帧数还是更像我称之为“世界滴答”? In case you want to run the game at >30fps and update the whole world state 30 times per second: Nope , I suppose you can forget doing that (we did a panic simulation with about 1,000 nodes/persons as part of a CUDA lecture. And while that's way more simple than your simulation, it was barely able to run in real time on a GTX780; so I'll assume a seasoned CUDA developer would most likely hit a limit with 10,000 nodes on that hardware - and you want to have >20x the nodes with more a way more complex AI/simulation than "run away from fire to the next visible exit and trample other people if your panic level is too high + simple 2D wall collision"). 如果你想以> 30fps运行游戏并且每秒更新整个世界状态30次: ,我想你可以忘记这样做(我们做了一个恐慌模拟,大约有1000个节点/人作为CUDA讲座的一部分。虽然这比你的模拟更简单,但它几乎无法在GTX780上实时运行;所以我假设一个经验丰富的CUDA开发人员很可能会在该硬件上达到10,000个节点的限制 - 你想拥有> 20x节点的AI /仿真更复杂,而不是“从火灾到下一个可见的出口逃跑,如果你的恐慌程度过高而踩踏其他人+简单的2D墙壁碰撞”)。

But if by frame you mean world simulation tick, then yes, this could be doable . 但是,如果你的框架意味着世界模拟滴答,那么是的,这可能是可行的

There are again some details missing from your questions now: Do you plan to develop a dedicated MMO server with >200k monsters and thousand players, or is it a local host single player? 现在你的问题还有一些细节缺失:你是否计划开发一个拥有> 20万怪物和数千名玩家的专用MMO服务器,或者它是一个本地主机单人游戏? Or something in between (networked multiplayer RPG, say max 16 players)? 或介于两者之间(网络多人RPG,最多16名玩家)?

If you only have a few players (I guess so, since you said 2D; and there isn't a huge difference between one player or four): Don't do all the simulation in full detail at once. 如果你只有几个玩家(我想是这样的,因为你说的是​​2D;并且一个玩家或者四个玩家之间没有太大差异): 不要一次完全详细地进行所有模拟。 For full immersion, it is enough to have a detailed simulation in vicinity of the player(s) . 对于完全沉浸,在玩家附近进行详细模拟就足够了 Like in pen&paper: As a game master (GM) you usually only have a few key events happening across the world, and you make up the rest as you go/have some rough outline what's happening elsewhere but no exact details. 就像纸笔一样:作为游戏大师(GM),你通常只会在世界各地发生一些关键事件,你可以在剩下的时间里完成剩下的工作/粗略概述其他地方发生的事情,但没有确切的细节。 If you're a good GM, that's convincing enough for the players, because who cares if the about the current position of a guard in some throne room 50 miles away? 如果你是一名出色的总经理,这对于球员来说足够有说服力,因为谁会关心50英里以外的某个王座室内卫兵的当前位置?

I have a feeling you want to do a "proper, fully simulated game with full social interaction between the NPCs/monster, because no one else is doing something like that" (correct me if I'm wrong), but there is a good reason no one is doing that: It's quite hard. 我有一种感觉,你想做一个“适当的,完全模拟的游戏,在NPC /怪物之间进行完全的社交互动,因为没有其他人做这样的事情”(如果我错了就纠正我),但是有一个好的没有人这样做的原因:这很难。

Idea: If you think in "zones", you only need to run the simulation in those zones the players are currently active. 想法:如果您在“区域”中思考,您只需要在玩家当前处于活动状态的区域中运行模拟。 All other zones are frozen. 所有其他区域都被冻结。 Once the player(s) switch zones, you can just unfreeze and fast-forward the new zone. 一旦玩家切换区域,您就可以解冻并快进新区域。 You can either do this in full detail, or approximate it. 您可以完整地详细说明,也可以近似它。 If you don't want loading screens, you can unfreeze and fast-forward zones in the vicinity of the players which they might enter. 如果您不想加载屏幕,可以解冻并快进他们可能进入的玩家附近的区域。

On top of that, you should thing about your architecture. 最重要的是,您应该了解您的架构。 For example, you mention you want to know what monster did trink potion X. Of course this is simple to write down in SQL, but you won't get happy with the performance. 例如,你提到你想知道什么怪物做了药水X.当然这很容易写在SQL中,但你不会对性能感到满意。 Or at least I don't think I would, and that's after one basic database lecture and a "let's write a high performance SQL server"-advanced-lecture [full disclosure: I'm bad at writing high performance SQL queries since I don't usually use SQL]. 或者至少我认为我不会,这是在一个基本的数据库讲座和“让我们编写一个高性能的SQL服务器”之后 - 高级讲座[完全披露:我不善于编写高性能的SQL查询,因为我不喜欢通常使用SQL]。 Plus: Who needs full ACID for a game? 另外:谁需要完整的ACID游戏? Okay, for simplicity you could put stuff you don't really need that often into a SQL DB (monster height, weight, flavor texts,...?), or ECS, or what-ever technique you deem best. 好的,为了简单起见,您可以将经常不需要的东西放入SQL DB(怪物高度,重量,味道文本,......?),或ECS,或您认为最好的技术。 But everything you want to touch every few seconds could go into memory. 但是你想要每隔几秒触摸的一切都可以进入记忆。 I mean, if you store 1kByte per monster, than you're at ~200MByte memory for 200k monsters. 我的意思是,如果你每个怪物存储1kByte,那么对于200k怪物来说你的存储量是200MByte。

Anyway, back to the question "which monsters did trink potion X?": Why would you want to know that? 无论如何,回到“哪些怪物叮叮魔药X?”的问题:你为什么要知道? To apply effects? 要应用效果? To check if effects wear of? 检查效果是否磨损? I'd be using a event queue for that: A monster drinks a potion of strength -> Update inventory, give it bonus STR, compute a timeout and queue a event "bonus STR wear of for ". 我将使用一个事件队列:一个怪物喝一剂力量 - >更新库存,给它奖金STR,计算一个超时并排队一个事件“奖金STR磨损为”。 That's probably even faster than processing 200MByte of memory, since you're only doing "what needs to be done", per tick - and not "checking everything for every possible condition, every tick". 这可能比处理200MByte的内存要快得多,因为你只需按照每个刻度执行“需要完成的工作” - 而不是“针对每个可能的条件检查所有内容,每个刻度”。

Also, think be careful with your algorithms: You have person X knows person Y relationship, annotated with "public/private"? 另外,请注意你的算法:你有人X知道人Y关系,用“公共/私人”注释? Depending on what you want to do on that graph you might run into NP-hard problems . 根据您在该图表上的操作,您可能会遇到NP难问题 For example you might have a bad time if you accidentally try to solve a derivative of the clique problem . 例如,如果您不小心尝试解决派系问题的衍生物,那么您可能会遇到不好的时间。

I could add more, but since the question is a bit vague I'll just stop here and hope you might get some good ideas. 我可以添加更多,但由于问题有点模糊,我会停在这里,希望你能得到一些好主意。

Based on the former answers/comments and your answers for them, I assume that you decided to continue using SQL but you want a better way for reading/processing it. 基于前面的答案/评论以及您对它们的回答,我假设您决定继续使用SQL,但您希望有更好的方法来阅读/处理它。 Therefore, my answer focuses that aspect only, and is related solely to design, rather than other suggestions (like using different SQL engines). 因此,我的回答只关注这个方面,而且仅与设计有关,而不是与其他建议(如使用不同的SQL引擎)有关。

The main issue in your DB, as it reflects in your original question, is that all the aspects of a given monster lay in a single record, all in the same table . 正如您在原始问题中所反映的那样,您的数据库中的主要问题是, 给定怪物的所有方面都位于单个记录中,所有这些都在同一个表中 That makes the table huge over time. 随着时间的推移,这使得桌子变得非 Besides that, this design makes your DB and code hard to maintain and evolve. 除此之外,这种设计使您的数据库和代码难以维护和发展。

While it might sound "right" to hold all the details of a monster in a single record (and, maybe, a single object that reflects it in the code itself), this is rarely a good decision. 虽然将怪物的所有细节保存在单个记录中可能听起来“正确”(并且,也许是在代码本身中反映它的单个对象),但这很少是一个好的决定。 You should have a clear separation between an object attributes as modeled in your "world" to those attributes modeled in software. 您应该将在“世界”中建模的对象属性与在软件中建模的属性明确分开。 For example, the location of a monster is probably being changed very frequently, while its name is not. 例如,怪物的位置可能会经常更改,而其名称则不然。 Since you hold all of the data in a single table, any change to the location is done on the very same table that holds all the other attributes, which makes it a very heavy action. 由于您将所有数据保存在单个表中,因此对位置的任何更改都在包含所有其他属性的同一个表上完成,这使得它成为非常繁重的操作。

What you should do, instead, is to: 相反,你应该做的是:

  • Separate the data into different tables 将数据分成不同的表
  • Choose good indices to each table 为每个表选择好的索引
  • Use joins as necessary (based, for example, on the monster's ID) 根据需要使用连接(例如,基于怪物的ID)

That way, any read and change is done only in the relevant context; 这样,任何读取和更改都只在相关的上下文中完成; for example, changing the location of a monster will only change the locations table, without affecting tables of more persistent details. 例如,更改怪物的位置只会更改位置表,而不会影响更持久的细节表。 Joins should not take lots of time, as long as you have good index and you filter only those data that interest you. 连接不应该花费很多时间,只要您有良好的索引并且只过滤那些您感兴趣的数据。 In addition to speeding up your queries, this design is much more flexible. 除了加快查询速度之外,这种设计更加灵活。 For example, if you want to add a new type of attribute to monsters, you can just add a new table, or use an existing table with similar data (eg monster's equipment) to add hold it. 例如,如果要向怪物添加新类型的属性,则可以添加新表,或使用具有类似数据的现有表(例如,怪物的设备)来添加它。

If your queries rely a lot on "geographic" locations, and you still want to handle it using a relational DB, you might consider other types of DBs that have better support in spatial queries. 如果您的查询在很大程度上依赖于“地理”位置,并且您仍希望使用关系数据库来处理它,则可以考虑在空间查询中具有更好支持的其他类型的DB。

HTH! HTH!

I wouldn't query the SQL databse at every frame, but instead cache the monsters that you think are the most likely to be needed in calculations. 我不会在每一帧查询SQL数据库,而是缓存 您认为最有可能在计算中需要的怪物。

How many monsters should I cache? 我应该缓存多少怪物?

If there is someone that knows, that is you! 如果有人知道,那就是你! However, I think that only by experimenting you are going to find a sweet spot for the size of your cache. 但是,我认为只有通过试验才能找到缓存大小的最佳位置。 You search for implementations of cache and get inspired, like this . 您可以搜索缓存的实现并获得灵感,就像这样


Why use SQL at first place? 为什么首先使用SQL? Consider writing to the disk. 考虑写入磁盘。


Your image suggests a graph, so why not store what you need as a graph? 您的图片会显示一个图表,那么为什么不将您需要的内容存储为图表? As you suggested, The Boost Graph Library (BGL) can come in handy! 正如您所建议的那样, Boost Graph Library(BGL)可以派上用场! Check this too: https://stackoverflow.com/questions/2751826/which-c-graph-library-should-i-use 检查一下: https//stackoverflow.com/questions/2751826/which-c-graph-library-should-useuse

There are 2 ways of limiting what is happening, and they can be used together. 有两种方法可以限制发生的事情,它们可以一起使用。

  1. Scheduling the events. 安排事件。
  2. Limiting the data to the visible screen. 将数据限制在可见屏幕。

Scheduling the events 安排事件

Assuming the monsters do not act every frame, the next action of a monster can be scheduled for N frames into the future, by working out just the actions which need to be performed on this frame, then the amount of monsters to be dealt with per-frame, can be reduced. 假设怪物不是每一帧都行动,怪物的下一个动作可以安排在未来的N帧中,只需要计算出需要在这个帧上执行的动作,然后是每个怪物要处理的怪物数量。 -frame,可以减少。

Limiting the data to the visible screen 将数据限制在可见屏幕

sqlite has an R*Tree to support selecting only those items within a geographical region. sqlite有一个R * Tree,支持只选择地理区域内的那些项目。

The screen can only show those monsters which are visible, this allows only those within the screen area have accurate simulation with other monsters getting a coarser simulation. 屏幕只能显示那些可见的怪物,这样只允许屏幕区域内的那些怪物与其他怪物进行精确模拟,从而获得更粗略的模拟。

As far as I can tell, World of Warcraft has a time equation for a monster's position .... 据我所知,魔兽世界有一个怪物位置的时间等式....

MyXY = getPosition( me, currentTime );

This would be the natural state of the monster when it is in its stable 'not viewed state'. 当它处于稳定的“未被观察状态”时,这将是怪物的自然状态。 This means actions for the monster don't need to be simulated at all when they are not visible. 这意味着当怪物不可见时,根本不需要模拟它们的动作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM