当前位置: 首页 > news >正文

再议AOI算法

AOI算法优化

首先,需要知道AOI(Area of Interest)是什么。AOI通常用于游戏或者分布式系统中,用来管理玩家或对象的视野范围,只关注感兴趣区域内的其他对象,减少不必要的通信和数据传输。

整理思路

如文章标题,我们要求结合网格和十字链表两种方法来实现AOI。网格法是将地图划分成多个格子,每个格子管理其中的对象,这样可以快速查找周围的对象。十字链表可能是指每个对象维护两个链表,一个横向(x轴)和一个纵向(y轴)的链表,方便快速插入和删除,同时遍历附近的节点。

接下来,我需要确定如何将这两种结构结合起来。可能的思路是,使用网格划分区域,每个网格内的对象用十字链表来管理。这样在查找周围对象时,可以快速定位到附近的网格,然后遍历这些网格中的十字链表,找到在视野范围内的对象。

首先,网格的实现。每个网格需要记录该区域的坐标范围,以及该区域内的对象链表。或者,可能每个对象的位置信息会被映射到对应的网格中,而每个网格维护两个方向的链表?或者,十字链表是全局的,按照坐标顺序排列,而网格用于快速定位邻近区域?

可能需要更详细的结构设计。例如,每个对象在加入AOI管理时,会被分配到对应的网格中。同时,这些对象在全局的十字链表中按x和y坐标排序,方便快速遍历附近的节点。当需要检测视野内的对象时,先通过网格确定可能处于视野范围内的区域,然后利用十字链表快速遍历这些区域内的对象,进行精确的距离判断。或者,十字链表本身用于维护每个轴上的对象顺序,这样在遍历时,可以根据坐标范围快速跳过不在视野内的对象。比如,在x轴上按坐标排序的链表,当查找某个x范围内的对象时,可以快速定位链表的起始和结束节点,然后遍历这部分节点,同时在y轴上进行同样的判断。

步骤1

现在,需要具体设计这两个结构。网格法的AOI通常将整个地图划分为多个小格子,每个对象属于某个格子,当对象移动时,检查是否跨越格子边界,如果是,则从一个格子移除,加入另一个格子。查找周围对象时,只需检查周围几个格子内的对象。这种方法效率高,但可能有精度问题,比如视野范围跨越多个格子时,需要检查多个格子。而十字链表可能是指,每个对象在x和y方向上有前驱和后继指针,形成一个有序的链表。这样,当需要查找某个坐标范围内的对象时,可以从当前位置向前和向后遍历,直到超出范围。这种方法可以高效地遍历特定区域内的对象,但插入和删除操作可能较慢,因为需要维持有序性。

结合这两种方法的话,可能需要用网格来管理空间分区,每个网格内的对象使用十字链表来维护。或者,使用十字链表来维护整个对象的全局顺序,而网格用于快速缩小搜索范围。可能需要进一步理清思路。假设网格用于空间划分,每个网格包含一个十字链表,管理该网格内的对象。当查找周围对象时,先确定目标周围的网格,然后遍历这些网格中的十字链表,检查每个对象是否在视野范围内。这可能不够高效,因为十字链表在这里可能没有发挥作用,反而每个网格内的对象可能用普通链表即可。

步骤2

另一种思路是,全局使用十字链表来维护所有对象在x和y轴上的顺序,同时使用网格来加速区域查询。例如,当需要查找某个区域内的对象时,利用网格快速定位到附近的格子,然后在这些格子中的十字链表中进行范围查询。这可能比较复杂,需要同时维护两种结构。或者,十字链表本身作为AOI的核心结构,每个对象在x和y轴上按坐标排序,形成两个方向的链表。这样,当需要查找某个区域内的对象时,可以沿着x链表找到起始点,然后遍历直到超出x范围,同时检查y坐标是否满足。这种方法不需要网格,但可能效率较低,尤其是在大规模对象的情况下。此时,网格可以作为辅助结构,例如将对象所在的网格记录下来,当对象移动时,如果跨网格,则更新网格信息,这样在查找时,可以快速定位到周围的网格,然后利用十字链表遍历这些网格中的对象。

这可能是一个可行的方案。具体来说,每个对象有一个位置,并被分配到对应的网格中。同时,所有对象在x和y轴上形成有序的十字链表。当需要查找视野内的对象时,先确定视野覆盖的网格,然后针对这些网格中的每个对象,检查是否在十字链表的视野范围内。

步骤3

现在,我需要具体设计这两个结构。例如,网格部分,每个网格包含一个十字链表的头节点,或者每个网格中的对象在x和y方向上有链表连接?或者,整个空间使用一个十字链表,而网格用于加速邻近区域的查询?假设整个空间使用一个大的十字链表,所有对象按x和y坐标排序,形成双向链表。当要查找某个区域内的对象时,可以从中间位置开始,向左右或上下遍历,直到超出范围。而网格的作用可能在这里是帮助快速定位到链表中的某个节点,从而减少遍历的时间。

不过这可能比较复杂。可能更合理的做法是,每个网格维护自己的十字链表,即在该网格内的对象按x和y坐标排序。这样,当需要查找周围的网格时,可以合并这些网格的十字链表,并快速遍历。但这样的话,每个网格的十字链表可能很小,维护起来可能没有太大必要。或许应该将整个空间分为网格,每个网格中的对象用普通的链表或集合保存,当需要查找时,遍历周围网格的对象,然后用十字链表结构来快速过滤出在视野范围内的对象。

步骤4

或许,正确的做法是,使用网格来分割空间,每个网格内的对象用十字链表来管理。例如,每个网格有一个水平链表和一个垂直链表,对象在加入网格时,按坐标插入到链表的适当位置。当需要查询某个区域时,首先确定覆盖的网格,然后对这些网格中的十字链表进行范围查询,比如在水平链表上找到x范围内的对象,同时在垂直链表上检查y范围。

或者,十字链表是全局的,所有对象按x和y坐标排序,形成两个方向的有序链表。同时,每个对象记录所在的网格。当对象移动时,如果跨网格,则更新网格信息,并从链表中删除再插入新的位置。这样,当需要查询视野范围内的对象时,可以先用网格确定周围的网格,然后利用十字链表快速遍历这些网格中的对象,或者直接遍历整个十字链表中视野范围内的部分。这可能结合了两种方法的优点:网格用于快速定位邻近区域,而十字链表用于快速遍历有序的对象,减少检查的数量。

现在,我需要考虑如何具体实现这两种结构的结合。例如,每个对象在加入时,根据其坐标被分配到对应的网格,同时被插入到全局的x和y链表中,保持有序。当对象移动时,检查是否还在原网格,如果不在,则移动到新网格,并在链表中调整位置。结合两种方法:首先使用网格快速筛选出可能位于视野范围内的网格,然后在这些网格中使用十字链表的有序性进行范围查询,比如在x链表中找到起点和终点,然后遍历这些节点,并检查y坐标是否符合条件。

这样的结合可能提高查询效率,因为网格减少了需要检查的网格数量,而十字链表的有序性使得在每个网格中可以快速定位到起始点,减少遍历次数。

接下来,我需要考虑如何用C++实现这样的结构。首先,定义Object类,每个对象有x和y坐标,以及前后左右指针用于十字链表。同时,每个对象可能属于一个网格,网格可以用一个二维的网格坐标来表示,比如每个网格的大小是固定的,比如每个格子是100x100单位。

Grid类可能需要管理多个网格,每个网格有一个对象列表。或者,每个网格维护两个链表,x链表和y链表,按坐标排序。或者,整个空间维护一个全局的十字链表,而每个对象同时属于某个网格。

尝试coding

将地图划分为网格,每个网格中的对象按x和y坐标维护两个链表(即十字链表)。这样,每个网格内的对象在x和y方向上是局部有序的。当需要查询某个区域时,首先收集所有相关的网格,然后对每个网格中的十字链表进行范围查询,这样每个网格内的查询可以利用有序链表快速定位。例如,假设视野范围是某个矩形区域,首先确定覆盖的网格,然后对每个网格中的x链表进行遍历,找到x在范围内的对象,同时检查y是否符合条件。这可能减少每个网格内的遍历次数,因为链表是有序的,可以快速跳过不符合条件的对象。这样的结构结合了网格的空间划分和十字链表的有序性,可能提高查询效率。

现在,开始编写代码的大纲。

首先,定义对象的结构,每个对象需要存储坐标,以及前后指针用于x和y链表:

struct AOIObject {int id;float x;float y;// 网格中的坐标int gridX;int gridY;// 十字链表指针AOIObject* xPrev;AOIObject* xNext;AOIObject* yPrev;AOIObject* yNext;
};

然后,网格的结构,每个网格维护x和y链表的头尾指针:

struct Grid {AOIObject* xHead;AOIObject* xTail;AOIObject* yHead;AOIObject* yTail;
};

AOI管理类需要管理所有的网格,以及提供插入、删除、移动对象的方法:

class AOIManager {
public:AOIManager(float gridWidth, float gridHeight, float mapWidth, float mapHeight);void AddObject(AOIObject* obj);void RemoveObject(AOIObject* obj);void UpdateObject(AOIObject* obj, float newX, float newY);std::vector<AOIObject*> GetNearbyObjects(AOIObject* obj, float radius);private:float gridWidth_;float gridHeight_;int gridCols_;int gridRows_;std::vector<std::vector<Grid>> grids_;// 其他辅助方法...
};

当添加对象时,计算其所在的网格,然后插入到对应的Grid的x和y链表中,保持有序:

void AOIManager::AddObject(AOIObject* obj) {// 计算网格坐标int gridX = static_cast<int>(obj->x / gridWidth_);int gridY = static_cast<int>(obj->y / gridHeight_);obj->gridX = gridX;obj->gridY = gridY;// 获取对应的网格Grid& grid = grids_[gridY][gridX];// 插入到x链表,按x坐标排序AOIObject* current = grid.xHead;AOIObject* prev = nullptr;while (current && current->x < obj->x) {prev = current;current = current->xNext;}if (prev) {prev->xNext = obj;obj->xPrev = prev;} else {grid.xHead = obj;obj->xPrev = nullptr;}if (current) {current->xPrev = obj;obj->xNext = current;} else {grid.xTail = obj;obj->xNext = nullptr;}// 同样的逻辑插入到y链表,按y坐标排序// ...
}

这里需要处理x和y两个方向的链表插入,保持有序。类似代码需要处理y链表。

当对象移动时,检查是否跨网格:

void AOIManager::UpdateObject(AOIObject* obj, float newX, float newY) {int newGridX = static_cast<int>(newX / gridWidth_);int newGridY = static_cast<int>(newY / gridHeight_);if (newGridX != obj->gridX || newGridY != obj->gridY) {// 需要移动网格RemoveObject(obj);obj->x = newX;obj->y = newY;AddObject(obj);} else {// 同一网格内,只需更新坐标,并调整链表中的位置obj->x = newX;obj->y = newY;// 调整x链表中的位置AdjustPositionInGridX(obj);// 调整y链表中的位置AdjustPositionInGridY(obj);}
}

调整链表位置的函数需要处理从链表中取出对象,然后重新插入到正确的位置。例如:

void AOIManager::AdjustPositionInGridX(AOIObject* obj) {	Grid& grid = grids_[obj->gridY][obj->gridX];// 从原位置断开if (obj->xPrev) {obj->xPrev->xNext = obj->xNext;} else {grid.xHead = obj->xNext;}if (obj->xNext) {obj->xNext->xPrev = obj->xPrev;} else {grid.xTail = obj->xPrev;}// 重新插入到正确的位置AOIObject* current = grid.xHead;AOIObject* prev = nullptr;while (current && current->x < obj->x) {prev = current;current = current->xNext;}if (prev) {prev->xNext = obj;obj->xPrev = prev;} else {grid.xHead = obj;obj->xPrev = nullptr;}if (current) {current->xPrev = obj;obj->xNext = current;} else {grid.xTail = obj;obj->xNext = nullptr;}
}

同样的逻辑适用于y链表。

查询附近的对象时,首先确定视野范围覆盖的网格:

std::vector<AOIObject*> AOIManager::GetNearbyObjects(AOIObject* obj, float radius) {float x = obj->x;float y = obj->y;float left = x - radius;float right = x + radius;float top = y + radius;float bottom = y - radius;int startGridX = static_cast<int>(left / gridWidth_);int endGridX = static_cast<int>(right / gridWidth_);int startGridY = static_cast<int>(bottom / gridHeight_);int endGridY = static_cast<int>(top / gridHeight_);startGridX = std::max(0, startGridX);endGridX = std::min(gridCols_ - 1, endGridX);startGridY = std::max(0, startGridY);endGridY = std::min(gridRows_ - 1, endGridY);std::vector<AOIObject*> result;for (int gy = startGridY; gy <= endGridY; ++gy) {for (int gx = startGridX; gx <= endGridX; ++gx) {Grid& grid = grids_[gy][gx];// 遍历该网格的x链表,找到在[left, right]范围内的对象AOIObject* current = grid.xHead;while (current) {if (current->x >= left && current->x <= right) {// 检查y是否在[bottom, top]范围内if (current->y >= bottom && current->y <= top) {// 计算实际距离是否在半径内(如果是圆形视野的话)float dx = current->x - x;float dy = current->y - y;if (dx*dx + dy*dy <= radius*radius) {result.push_back(current);}}} else if (current->x > right) {break; // x链表是有序的,后面的都大于right,无需继续}current = current->xNext;}}}return result;
}

这样,每个网格中的x链表是有序的,所以一旦当前节点的x超过right就可以停止遍历该网格的链表。这减少了需要检查的节点数量。y坐标的判断则是在每个符合条件的x节点上进行。这可能是一个有效的结合网格和十字链表的AOI实现。每个网格内的x和y方向链表是有序的,因此在查询时,可以快速跳过不符合条件的对象,提高效率。

现在,需要验证这个设计是否正确,是否存在问题。例如,当对象移动时,如果它还在同一个网格中,那么需要调整在链表中的位置,这需要正确地从链表中移除并重新插入。如果网格的大小设置得比较大,那么每个网格中的对象数量可能较多,链表的插入和删除操作可能比较耗时。如果网格设置较小,则链表操作较快,但需要处理更多的网格在查询时。

可能存在的优化点:当对象在同一个网格内移动时,调整链表的位置,但由于网格内的链表是有序的,移动可能导致频繁的链表调整。如果网格足够小,对象在同一网格内的移动可能不会导致频繁的链表重排,因为坐标变化不大。或者,如果网格较大,可能链表中的顺序调整会更频繁。

总的来说,这样的设计结合了网格的空间划分和链表的有序性,可以在查询时快速过滤对象,减少需要检查的数量,同时网格帮助缩小搜索范围。

现在,编写一个具体的例子,比如创建一些对象,并测试AOI功能。例如:

int main() {AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f); // 每个网格100x100,地图大小1000x1000AOIObject obj1{1, 50.0f, 50.0f};AOIObject obj2{2, 150.0f, 150.0f};AOIObject obj3{3, 250.0f, 250.0f};aoi.AddObject(&obj1);aoi.AddObject(&obj2);aoi.AddObject(&obj3);// 查询obj1附近半径100的对象auto nearby = aoi.GetNearbyObjects(&obj1, 100.0f);for (auto obj : nearby) {std::cout << "Nearby object id: " << obj->id << std::endl;}// 移动obj2到更远的位置aoi.UpdateObject(&obj2, 300.0f, 300.0f);nearby = aoi.GetNearbyObjects(&obj1, 100.0f);// 现在应该只有obj1自己,或者包括其他对象?// 需要具体计算距离return 0;
}

这只是一个简单示例,实际需要根据坐标和半径计算距离是否正确。

可能的问题:在网格划分时,是否正确地处理了边界?例如,当对象的x坐标正好是网格的边界时,是否分配到正确的网格?在计算gridX和gridY时,应该使用整数除法,例如,假设网格的索引从0开始,每个网格的宽度是100,那么坐标099属于gridX=0,100199属于gridX=1,等等。因此,计算gridX时应为static_cast(x / gridWidth_),这可能需要处理浮点数精度问题。

另外,在GetNearbyObjects函数中,是否考虑了所有可能的覆盖网格?比如,当视野半径覆盖多个网格时,是否正确遍历所有相关的网格?

综上,这样的代码框架结合了网格和十字链表两种结构,网格用于空间划分,每个网格内的对象按x和y坐标维护有序链表,以提高查询效率。这应该满足我们的需求。

综合的代码

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>struct AOIObject {int id;float x, y;int gridX, gridY;AOIObject* xPrev = nullptr;AOIObject* xNext = nullptr;AOIObject* yPrev = nullptr;AOIObject* yNext = nullptr;AOIObject(int id, float x, float y) : id(id), x(x), y(y) {}
};struct Grid {AOIObject* xHead = nullptr;AOIObject* xTail = nullptr;AOIObject* yHead = nullptr;AOIObject* yTail = nullptr;
};class AOIManager {
private:float gridWidth, gridHeight;int gridCols, gridRows;std::vector<std::vector<Grid>> grids;public:AOIManager(float gw, float gh, float mapWidth, float mapHeight): gridWidth(gw), gridHeight(gh) {gridCols = static_cast<int>(ceil(mapWidth / gw));gridRows = static_cast<int>(ceil(mapHeight / gh));grids.resize(gridRows, std::vector<Grid>(gridCols));}// 插入对象到网格并维护有序链表void AddObject(AOIObject* obj) {UpdateGridPosition(obj);InsertToGrid(obj);}// 从网格中移除对象void RemoveObject(AOIObject* obj) {RemoveFromGrid(obj);}// 更新对象位置void UpdateObject(AOIObject* obj, float newX, float newY) {RemoveFromGrid(obj);obj->x = newX;obj->y = newY;UpdateGridPosition(obj);InsertToGrid(obj);}// 获取周围对象std::vector<AOIObject*> GetNearbyObjects(AOIObject* obj, float radius) {std::vector<AOIObject*> result;float x = obj->x, y = obj->y;int minGX = std::max(0, static_cast<int>((x - radius) / gridWidth));int maxGX = std::min(gridCols-1, static_cast<int>((x + radius) / gridWidth));int minGY = std::max(0, static_cast<int>((y - radius) / gridHeight));int maxGY = std::min(gridRows-1, static_cast<int>((y + radius) / gridHeight));for (int gy = minGY; gy <= maxGY; ++gy) {for (int gx = minGX; gx <= maxGX; ++gx) {CheckGrid(gy, gx, x, y, radius, result);}}return result;}private:// 更新对象所属网格void UpdateGridPosition(AOIObject* obj) {obj->gridX = static_cast<int>(obj->x / gridWidth);obj->gridY = static_cast<int>(obj->y / gridHeight);obj->gridX = std::clamp(obj->gridX, 0, gridCols-1);obj->gridY = std::clamp(obj->gridY, 0, gridRows-1);}// 将对象插入到对应网格的有序链表中void InsertToGrid(AOIObject* obj) {Grid& grid = grids[obj->gridY][obj->gridX];// X轴插入if (!grid.xHead || obj->x < grid.xHead->x) {obj->xNext = grid.xHead;if (grid.xHead) grid.xHead->xPrev = obj;grid.xHead = obj;} else {AOIObject* current = grid.xHead;while (current->xNext && current->xNext->x < obj->x) {current = current->xNext;}obj->xNext = current->xNext;if (current->xNext) current->xNext->xPrev = obj;current->xNext = obj;obj->xPrev = current;}// Y轴插入(类似X轴逻辑)// ...}// 从链表中移除对象void RemoveFromGrid(AOIObject* obj) {Grid& grid = grids[obj->gridY][obj->gridX];// X轴移除if (obj->xPrev) obj->xPrev->xNext = obj->xNext;else grid.xHead = obj->xNext;if (obj->xNext) obj->xNext->xPrev = obj->xPrev;else grid.xTail = obj->xPrev;// Y轴移除// ...}// 检查指定网格内的对象void CheckGrid(int gy, int gx, float x, float y, float r, std::vector<AOIObject*>& result) {Grid& grid = grids[gy][gx];AOIObject* current = grid.xHead;float rSq = r * r;while (current) {float dx = current->x - x;if (dx > r) break;  // X轴超出范围float dy = current->y - y;if (dx*dx + dy*dy <= rSq) {result.push_back(current);}current = current->xNext;}}
};int main() {AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f);AOIObject obj1(1, 50.0f, 50.0f);AOIObject obj2(2, 150.0f, 150.0f);AOIObject obj3(3, 160.0f, 160.0f);aoi.AddObject(&obj1);aoi.AddObject(&obj2);aoi.AddObject(&obj3);// 查询obj1周围半径100的对象auto nearby = aoi.GetNearbyObjects(&obj1, 100.0f);std::cout << "Nearby objects count: " << nearby.size() << "\n";for (auto obj : nearby) {std::cout << "Object " << obj->id << " at (" << obj->x << ", " << obj->y << ")\n";}return 0;
}

实现说明:

  1. 网格划分:将地图划分为固定大小的网格,每个网格维护两个有序链表(X/Y方向)
  2. 十字链表:每个网格中的对象按坐标值在X/Y方向形成有序链表
  3. 对象管理
    • 添加/删除时维护链表的有序性
    • 移动时自动更新网格归属
  4. 范围查询
    • 先确定需要检查的网格范围
    • 利用链表有序性快速过滤对象

优势:

  1. 网格优化:快速缩小检测范围
  2. 链表有序性:减少实际距离计算次数
  3. 动态更新:位置变化时自动维护数据结构

补全代码并优化

示例代码只实现了X轴的链表,而Y轴的插入和删除逻辑被省略了。此外,我们的扩展建议包括优化链表操作、添加进入离开视野的回调,以及支持不同形状的检测区域。我需要逐一分析这些需求,并考虑如何在代码中实现。

首先,第一个扩展建议是“添加Y轴链表处理”。在之前的代码中,InsertToGrid和RemoveFromGrid函数只处理了X轴的链表,而Y轴的逻辑被注释掉了。所以,我需要补全这部分代码,确保在插入和删除对象时,同时维护X和Y两个方向的有序链表。这需要仔细检查插入和删除的逻辑,确保两个链表的指针正确更新,避免出现悬空指针或链表断裂的情况。

接下来是“优化链表插入/删除操作”。当前代码中使用的是线性搜索来找到插入位置,这在最坏情况下时间复杂度是O(n)。如果对象的位置经常变化,这样的效率可能不够。优化方法可以考虑使用跳表或者平衡二叉树,但考虑到实现的复杂性,可能在示例代码中保持简单,或者采用更高效的搜索方式,比如二分查找,但由于链表结构不支持随机访问,可能需要调整数据结构,比如使用双向链表的同时记录中间节点,但这会增加复杂度。因此,可能暂时保持原样,但添加注释说明可能的优化方向。

第三个建议是“添加对象离开/进入视野的回调”。这需要AOIManager能够跟踪对象的移动,并在对象进入或离开其他对象的视野时触发回调函数。实现这一点需要在对象的位置更新时,比较新旧网格周围的区域,找出新增和移除的对象,然后触发相应的回调。这涉及到维护每个对象的观察者列表,或者全局的事件处理机制。考虑到代码示例的简洁性,可能需要设计一个回调接口,让用户注册进入和离开的事件处理函数,并在GetNearbyObjects或UpdateObject时触发这些回调。

最后一个建议是“支持不同形状的检测区域”。当前的GetNearbyObjects使用圆形区域,将来可能希望支持矩形、扇形等其他形状。这需要修改CheckGrid函数中的距离计算部分,根据不同的形状进行判断。例如,矩形区域只需要检查坐标是否在特定范围内,而扇形可能需要计算角度。这部分可以通过策略模式,将区域检测逻辑抽象为不同的类,根据传入的参数选择不同的检测策略。但在示例代码中,可能需要保持简单,添加多个检测函数,如GetNearbyInRectangle或GetNearbyInSector。

现在,我需要考虑如何逐步实现这些扩展,并在代码中展示出来。首先,补全Y轴链表的处理,确保插入和删除时同时更新X和Y链表。然后,优化插入操作,可能保持线性搜索但确保代码正确性。接着,添加回调机制,在对象移动时比较新旧附近的物体,触发进入和离开事件。最后,修改GetNearbyObjects以支持不同形状的区域,比如添加一个形状参数,或者不同的函数。

在实现回调时,需要注意避免在回调函数中修改AOIManager的状态,以免引起不可预见的错误。此外,处理进入和离开事件需要记录之前的附近对象列表,并在更新后比较差异。这可能需要为每个对象维护一个上一次查询的结果,或者在每次更新时比较新旧两个结果。

对于不同形状的区域,可能需要修改CheckGrid函数中的条件判断。例如,矩形区域只需检查x和y的差值是否在范围内,而无需计算平方距离。扇形区域则需要计算角度和距离,这可能更复杂。

在编写代码时,需要保持示例的简洁性和可读性,避免过度设计,同时展示关键部分的实现。例如,在补全Y轴链表时,可以复制X轴的逻辑,但需要修改变量为y方向的。在添加回调时,可以定义两个函数指针或使用C++的std::function来允许用户注册回调函数。

可能遇到的挑战包括正确处理链表的双向指针,确保在插入和删除时不会遗漏任何指针的更新,以及在处理回调时高效地比较对象集合的变化。此外,支持不同形状的区域需要灵活的条件判断,可能需要使用策略模式或模板方法,但在示例中可能直接通过参数控制。

总结步骤:

  1. 补全Y轴链表的插入和删除逻辑。
  2. 在InsertToGrid和RemoveFromGrid中同时处理X和Y链表。
  3. 添加回调接口,处理进入和离开事件。
  4. 修改GetNearbyObjects以支持不同形状的区域,或添加新的方法。
  5. 在示例代码中演示这些扩展功能的使用。

需要确保每一步的代码正确,并进行测试,例如在main函数中添加移动对象并触发回调的案例。同时,需要处理可能的边界条件,例如对象移动到网格边界时的处理,或者多个对象在同一位置的情况。

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <functional>
#include <unordered_set>// 区域形状基类
class AOIStrategy {
public:virtual bool Check(float dx, float dy) const = 0;virtual ~AOIStrategy() = default;
};// 圆形区域策略
class CircleStrategy : public AOIStrategy {float radiusSq;
public:explicit CircleStrategy(float r) : radiusSq(r*r) {}bool Check(float dx, float dy) const override {return dx*dx + dy*dy <= radiusSq;}
};// 矩形区域策略
class RectangleStrategy : public AOIStrategy {float width, height;
public:RectangleStrategy(float w, float h) : width(w), height(h) {}bool Check(float dx, float dy) const override {return std::abs(dx) <= width/2 && std::abs(dy) <= height/2;}
};struct AOIObject {int id;float x, y;int gridX, gridY;AOIObject* xPrev = nullptr;AOIObject* xNext = nullptr;AOIObject* yPrev = nullptr;AOIObject* yNext = nullptr;AOIObject(int id, float x, float y) : id(id), x(x), y(y) {}
};struct Grid {AOIObject* xHead = nullptr;AOIObject* xTail = nullptr;AOIObject* yHead = nullptr;AOIObject* yTail = nullptr;
};class AOIManager {
private:float gridWidth, gridHeight;int gridCols, gridRows;std::vector<std::vector<Grid>> grids;// 事件回调std::function<void(AOIObject*, AOIObject*)> onEnter;std::function<void(AOIObject*, AOIObject*)> onLeave;// 优化插入的有序性维护AOIObject* InsertSorted(AOIObject* head, AOIObject* obj, bool isXAxis) {if (!head) return obj;AOIObject** nextPtr = isXAxis ? &head->xNext : &head->yNext;AOIObject** prevPtr = isXAxis ? &head->xPrev : &head->yPrev;float compareVal = isXAxis ? obj->x : obj->y;// 头节点处理if ((isXAxis ? head->x : head->y) > compareVal) {obj->xNext = head;head->xPrev = obj;return obj;}AOIObject* current = head;while (current->xNext && (isXAxis ? current->xNext->x : current->xNext->y) < compareVal) {current = isXAxis ? current->xNext : current->yNext;}obj->xNext = current->xNext;if (current->xNext) {current->xNext->xPrev = obj;}current->xNext = obj;obj->xPrev = current;return head;}public:AOIManager(float gw, float gh, float mapWidth, float mapHeight): gridWidth(gw), gridHeight(gh) {gridCols = static_cast<int>(ceil(mapWidth / gw));gridRows = static_cast<int>(ceil(mapHeight / gh));grids.resize(gridRows, std::vector<Grid>(gridCols));}// 注册事件回调void SetCallbacks(std::function<void(AOIObject*, AOIObject*)> enter,std::function<void(AOIObject*, AOIObject*)> leave) {onEnter = enter;onLeave = leave;}// 带事件通知的更新方法void UpdateObjectWithEvent(AOIObject* obj, float newX, float newY) {auto oldNearby = GetNearbyObjects(obj, 100.0f); // 假设视野半径100RemoveFromGrid(obj);obj->x = newX;obj->y = newY;UpdateGridPosition(obj);InsertToGrid(obj);auto newNearby = GetNearbyObjects(obj, 100.0f);// 计算进入和离开的对象std::unordered_set<AOIObject*> oldSet(oldNearby.begin(), oldNearby.end());std::unordered_set<AOIObject*> newSet(newNearby.begin(), newNearby.end());for (auto* o : newNearby) {if (!oldSet.count(o) && o != obj && onEnter) {onEnter(obj, o);}}for (auto* o : oldNearby) {if (!newSet.count(o) && o != obj && onLeave) {onLeave(obj, o);}}}// 支持不同形状的查询std::vector<AOIObject*> QueryArea(AOIObject* center, const AOIStrategy& strategy) {std::vector<AOIObject*> result;float x = center->x, y = center->y;int minGX = std::max(0, static_cast<int>((x - strategy.GetLeft()) / gridWidth));int maxGX = std::min(gridCols-1, static_cast<int>((x + strategy.GetRight()) / gridWidth));int minGY = std::max(0, static_cast<int>((y - strategy.GetBottom()) / gridHeight));int maxGY = std::min(gridRows-1, static_cast<int>((y + strategy.GetTop()) / gridHeight));for (int gy = minGY; gy <= maxGY; ++gy) {for (int gx = minGX; gx <= maxGX; ++gx) {CheckGridWithStrategy(gy, gx, x, y, strategy, result);}}return result;}private:// 带策略的网格检查void CheckGridWithStrategy(int gy, int gx, float x, float y, const AOIStrategy& strategy,std::vector<AOIObject*>& result) {Grid& grid = grids[gy][gx];// 同时利用X和Y轴有序性进行快速筛选AOIObject* current = grid.xHead;while (current) {float dx = current->x - x;float dy = current->y - y;if (strategy.Check(dx, dy)) {result.push_back(current);}current = current->xNext;}}// 完整实现Y轴插入void InsertToGrid(AOIObject* obj) {Grid& grid = grids[obj->gridY][obj->gridX];// X轴插入grid.xHead = InsertSorted(grid.xHead, obj, true);// Y轴插入grid.yHead = InsertSorted(grid.yHead, obj, false);}// 完整实现Y轴移除void RemoveFromGrid(AOIObject* obj) {Grid& grid = grids[obj->gridY][obj->gridX];// X轴移除if (obj->xPrev) obj->xPrev->xNext = obj->xNext;else grid.xHead = obj->xNext;if (obj->xNext) obj->xNext->xPrev = obj->xPrev;// Y轴移除if (obj->yPrev) obj->yPrev->yNext = obj->yNext;else grid.yHead = obj->yNext;if (obj->yNext) obj->yNext->yPrev = obj->yPrev;}// 其他原有方法保持不变...
};// 示例用法
int main() {AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f);// 设置事件回调aoi.SetCallbacks([](AOIObject* a, AOIObject* b) {std::cout << "Object " << a->id << " enter " << b->id << "\n";},[](AOIObject* a, AOIObject* b) {std::cout << "Object " << a->id << " leave " << b->id << "\n";});AOIObject obj1(1, 50.0f, 50.0f);AOIObject obj2(2, 150.0f, 150.0f);aoi.AddObject(&obj1);aoi.AddObject(&obj2);// 移动对象并触发事件aoi.UpdateObjectWithEvent(&obj1, 180.0f, 180.0f);// 使用不同形状查询RectangleStrategy rect(80.0f, 80.0f);auto inRect = aoi.QueryArea(&obj1, rect);std::cout << "Objects in rectangle: " << inRect.size() << "\n";return 0;
}

扩展功能说明:

  1. Y轴链表完整实现
    • InsertToGridRemoveFromGrid中同时维护X/Y方向的有序链表
    • 新增通用插入方法InsertSorted处理排序逻辑
  2. 事件回调系统
    • 使用std::function支持自定义进入/离开事件
    • UpdateObjectWithEvent中通过新旧集合对比触发事件
    • 支持对象间的相互感知通知
  3. 策略模式区域检测
    • 定义AOIStrategy基类实现不同形状检测
    • 实现圆形和矩形两种策略
    • 新增QueryArea方法支持任意形状查询
  4. 性能优化
    • 双向链表维护头尾指针加速边界操作
    • 利用坐标有序性提前终止无效检测
    • 支持多轴联合筛选优化查询效率

使用建议:

事件管理

// 注册自定义回调
aoi.SetCallbacks([](AOIObject* watcher, AOIObject* target) {// 处理进入视野逻辑},[](AOIObject* watcher, AOIObject* target) {// 处理离开视野逻辑}
);

自定义区域形状

// 实现扇形区域策略
class SectorStrategy : public AOIStrategy {float radius;float angle;
public:SectorStrategy(float r, float a) : radius(r), angle(a) {}bool Check(float dx, float dy) const override {float dist = sqrt(dx*dx + dy*dy);if (dist > radius) return false;float objAngle = atan2(dy, dx);return fabs(objAngle) <= angle/2;}
};// 使用自定义策略
SectorStrategy sector(100.0f, M_PI/3); // 60度扇形
auto inSector = aoi.QueryArea(&obj1, sector);

性能调优

// 调整网格大小平衡查询与维护开销
AOIManager aoi(50.0f, 50.0f, 1000.0f, 1000.0f); // 更细密的网格// 使用空间索引加速查询
void OptimizedInsert(AOIObject* obj) {// 结合空间索引(如四叉树)进行多层索引
}

相关文章:

再议AOI算法

AOI算法优化 首先&#xff0c;需要知道AOI&#xff08;Area of Interest&#xff09;是什么。AOI通常用于游戏或者分布式系统中&#xff0c;用来管理玩家或对象的视野范围&#xff0c;只关注感兴趣区域内的其他对象&#xff0c;减少不必要的通信和数据传输。 整理思路 如文章…...

Unreal5 从入门到精通之如何实现 离线语音识别

文章目录 前言将语言服务器作为外部进程运行UE 项目设置开始捕获语音同时运行服务器进程和游戏进程将 SoundWave 而不是麦克风作为输入将数据发送到语言服务器节点的工作原理详细文档前言 今天我们要说的是一个语音转文本(STT)的插件 Offline Speech Recognition, 它支持离线…...

form-create-designer中$inject参数的数据结构及各项属性说明

FcDesigner 是一款基于Vue的开源低代码可视化表单设计器工具&#xff0c;通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单&#xff0c;提高开发者对表单的开发效率&#xff0c;节省开发者的时间。并广泛应用于在政务系统、OA系统、ERP系统、电商系统、流程管理等领域。 …...

WHAT - CSS 中的 min-height

文章目录 语法常见用途1. 防止元素被压缩得太小2. 配合 Flexbox 保证高度3. 用于内容区域动态撑高但不塌陷 与其他属性的区别提示 在 WHAT - CSS 中的 min-width 中我们已经详细介绍过 width。对于高度&#xff0c; CSS 同样提供一个 min-height. min-height 是 CSS 中用于设置…...

畅游Diffusion数字人(30):情绪化数字人视频生成

畅游Diffusion数字人(0)&#xff1a;专栏文章导航 前言&#xff1a;仅从音频生成此类运动极具挑战性&#xff0c;因为它在音频和运动之间存在一对多的相关性。运动视频的情绪是多元化的选择&#xff0c;之前的工作很少考虑情绪化的数字人生成。今天解读一个最新的工作FLOAT&…...

PLC系统中开关量与模拟量信号解析

引言 在现代工业自动化进程中&#xff0c;可编程逻辑控制器&#xff08;PLC&#xff09;凭借其强大的功能与灵活性&#xff0c;成为工业控制系统的核心设备。PLC能够高效、精准地控制工业生产流程&#xff0c;很大程度上依赖于其对开关量和模拟量信号的处理能力。深入理解这两…...

Qt中解决Tcp粘包问题

Qt中解决Tcp粘包问题 Qt中解决Tcp粘包问题——以文件发送为例服务器端客户端效果演示注意点 Qt中解决Tcp粘包问题——以文件发送为例 创建的工程如下图所示&#xff1a; 服务器端 界面的布局以及名称如下图所示&#xff1a; 并且在Qt中增加网络模块 QT core gui n…...

Qt调用librdkafka

Qt调用librdkafka Windows系统编译Qt使用的kafka(librdkafka) VS2017编译librdkafka 2.1.0 经过上面的步骤我已经编译好了librdkafka库,我编译的主要十release版的,需要debug版的小伙伴编译的时候要留意一下。 接下来就是调用我们编译的kafka库了。 一、环境介绍 Qt:…...

深入解析Node.js文件系统(fs模块):从基础到进阶实践

文章目录 引言一、核心能力解析1.文件读写操作2.文件复制方案对比3.文件监控机制 二、扩展知识体系1.高级文件操作2.性能优化策略3.安全实践指南 三、最佳实践总结 引言 在 Node.js 生态系统中&#xff0c;fs 模块是与文件系统交互的核心工具。本文将通过代码示例和实践经验&a…...

9、AI测试辅助-代码Bug分析提示词优化

AI测试辅助-优化代码Bug分析提示词 Bug分析1、优化代码2、根据报错结果定位 Bug分析 利用AI优化代码Bug&#xff0c;有两种方式&#xff0c;一种是优化潜在的问题&#xff0c;一种是根据执行后的报错进行查找定位优化。其中如何用好提示词是关键 1、优化代码 常见需要优化的…...

AI无法解决的Bug系列(一)跨时区日期过滤问题

跨时区开发中&#xff0c;React Native如何处理新西兰的日期过滤问题 有些Bug&#xff0c;不是你写错代码&#xff0c;而是现实太魔幻。 比如我最近给新西兰客户开发一个React Native应用&#xff0c;功能非常朴素&#xff1a;用户选一个日期范围&#xff0c;系统返回该范围内…...

leetcode 153. Find Minimum in Rotated Sorted Array

题目描述 分析 可以发现一个规律&#xff1a; 假如整个数组最后一个元素是x。 最小值左侧&#xff08;不含最小值自己&#xff09;的元素全部大于x。 最小值右侧&#xff08;包含最小值自己&#xff0c;不包含x&#xff09;的元素全部小于x。 如果整个数组是有序的&#x…...

Brave 连接 Websocket 失败

前提: websocket 的服务启动正常连接的url是: ws://localhost: 15000/[子url] 在 Brave 浏览器的 console 中看到错误: WebSocket connection to ws://localhost:15000/ws failed:解决方法&#xff08;Brave 浏览器专用&#xff09; 方法 1&#xff1a;关闭 Brave 的 Shiel…...

【设计模式】基于 Java 语言实现工厂模式

目录 一、简单工厂模式 1.1 简单工厂模式的介绍 二、工厂方法模式 2.1 工厂方法模式的介绍 2.2 工厂方法模式的基本实现 2.3 工厂方法模式的应用场景 三、抽象工厂 3.1 抽象工厂的概念 3.2 抽象工厂的基本结构 3.3 抽象工厂的基本实现 3.4 抽象工厂的应用场景 四、…...

94.LabelGrid 的遍历与属性编辑 Maui例子 C#例子

for (int i 0; i < LabelGrid.Children.Count; i) {if (LabelGrid.Children[i] is Label label){await MainThread.InvokeOnMainThreadAsync(() >{label.TextColor Colors.Gray;});} } await Task.Delay(1000); // 延迟1秒 if (currentValue 0) {currentValue 16; } …...

Https流式输出一次输出一大段,一卡一卡的-解决方案

【背景】 最近遇到一个奇怪的现象&#xff0c;前端vue&#xff0c;后端python&#xff0c;服务部署在服务器上面后&#xff0c;本来一切正常&#xff0c;但公司说要使用https访问&#xff0c;想着也没什么问题&#xff0c;切过去发现在没有更改任何代码的情况下&#xff0c;ht…...

【C# 自动化测试】Selenium显式等待机制详解

Selenium显式等待机制详解 一、显式等待的概念 在自动化测试中&#xff0c;等待机制是处理页面元素加载延迟的重要手段。显式等待允许我们在继续执行代码之前等待某个条件发生&#xff0c;这比固定的强制等待更灵活高效。 二、显式等待的实现代码 1. 核心等待方法 /// <…...

【Redis】哈希表结构

目录 1、背景2、哈希表【1】底层结构【2】哈希冲突【3】链地址法【4】传统rehash【5】渐进式rehash【6】rehash触发条件【7】特性 1、背景 redis中的hashtable&#xff08;哈希表&#xff09;是一种高效的键值对存储结构&#xff0c;主要用于实现redis的字典类型&#xff0c;接…...

Redisson中为什么用lua脚本不用事务

一文详解事务和lua脚本的区别 核心问题&#xff1a; 为什么 Redisson 在实现分布式锁、信号量等复杂对象时&#xff0c;倾向于使用 Lua 脚本&#xff0c;而不是 Redis 内建的事务 (MULTI/EXEC)&#xff1f; 结论概览&#xff1a; Lua 脚本为 Redisson 提供了更强的原子性保证、…...

成功解决!!!Ubuntu系统安装包时出现:dpkg: 处理归档XXX时出错

在Ubuntu系统中在安装新的包时&#xff0c;有时会报错连环依赖问题&#xff0c;常见的报错为&#xff1a;下列软件包有未满足的依赖关系&#xff1a;XXX依赖XXX 但是它不会被安装 E: 有未能满足的依赖关系。请尝试不指明软件包的名字来运行“apt --fix-broken install”(也可以…...

MySql数据库连接池

C数据库连接池 前言1.MySql API 函数讲解1.1 连接数据库的步骤1.2 MySQL C API1.2.1 初始化连接环境1.2.2 连接mysql服务器1.2.3 执行sql语句1.2.4 获取结果集1.2.5 得到结果集的列数1.2.6 获取表头 -> 列名(字段名)1.2.7 得到结果集中各个字段的长度(字节为单位)1.2.8 遍历…...

C++之fmt库介绍和使用(2)

C之fmt库介绍与使用(2) Author: Once Day Date: 2025年5月19日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 …...

Python的collections模块:数据结构的百宝箱

Python的collections模块&#xff1a;数据结构的百宝箱 对话实录 小白&#xff1a;处理数据时&#xff0c;Python自带的数据结构不够用&#xff0c;有更强大的工具吗&#xff1f; 专家&#xff1a;那可不能错过collections模块&#xff0c;它提供了许多高效实用的数据结构&am…...

吃透 Golang 基础:数据结构之数组

文章目录 吃透 Golang 基础&#xff1a;数据结构之数组概述初始化访问和赋值小结参考资料 吃透 Golang 基础&#xff1a;数据结构之数组 对于 Golang 当中的顺序数据结构&#xff0c;使用频率最高的当然是切片&#xff0c;因为切片非常的灵活。与之相对比&#xff0c;数组常常会…...

第三个小程序动工:一款结合ai的菜谱小程序

1.环境搭建&#xff0c;与初步运行 安装及使用 | Taro 文档 找到一个合适的文件夹&#xff0c;cmd D:\gitee>pnpm install -g tarojs/cli╭──────────────────────────────────────────╮│ …...

小程序涉及提供提供文本深度合成技术,请补充选择:深度合成-AI问答类目

一、问题描述 最近新项目AI咨询小程序审核上线&#xff0c;按照之前小程序的流程&#xff0c;之前审核&#xff0c;提示审核不通过&#xff0c;审核不通过的原因&#xff1a;小程序涉及提供提供文本深度合成技术 (如: AI问答) 等相关服务&#xff0c;请补充选择&#xff1a;深…...

数据结构测试模拟题(1)

1、约瑟夫问题 #include<bits/stdc.h> using namespace std; const int N25; int e[N],ne[N],head-1,idx1; int n,m; void add_to_head(int x){e[idx]x;ne[idx]head;headidx; } void add(int k,int x){e[idx]x;ne[idx]ne[k];ne[k]idx; } int main(){cin>>n>>…...

Elasticsearch高级面试题汇总及答案

Elasticsearch高级面试题汇总及答案 这套Elasticsearch面试题汇总大全,希望对大家有帮助哈~ 1、什么是Elasticsearch Analyzer? 分析器用于文本分析,它可以是内置分析器也可以是自定义分析器。 2、Elasticsearch 支持哪些配置管理工具? 1、 Ansible 2、 Chef 3、 Pu…...

界面控件DevExpress WinForms v24.2——PDF Viewer功能升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…...

Apache Apisix配置ip-restriction插件以限制IP地址访问

介绍 ip-restriction 插件可以通过将 IP 地址列入白名单或黑名单来限制对服务或路由的访问。 支持对单个 IP 地址、多个 IP 地址和类似 10.10.10.0/24 的 CIDR&#xff08;无类别域间路由&#xff09;范围的限制。 属性 参数名类型必选项默认值有效值描述whitelistarray[st…...

Maven 项目打包时添加本地 Jar 包

在 Maven 项目开发中&#xff0c;我们经常会遇到需要引入本地 Jar 包的场景&#xff0c;比如使用未发布到中央仓库的第三方库、公司内部自定义工具包&#xff0c;或者处理版本冲突的依赖项。本文将详细介绍如何通过 Maven 命令将本地 Jar 包安装到本地仓库&#xff0c;并在项目…...

JavaScript 性能优化:调优策略与工具使用

引言 在当今的 Web 开发领域&#xff0c;性能优化已不再是锦上添花&#xff0c;而是产品成功的关键因素。据 Google 研究表明&#xff0c;页面加载时间每增加 3 秒&#xff0c;跳出率将提高 32%。而移动端用户如果页面加载超过 3 秒&#xff0c;有 53% 的用户会放弃访问。性能…...

48、c# 中 IList 接⼝与List的区别是什么?

在 C# 中&#xff0c;IList 接口和 List 类在集合操作中扮演不同角色&#xff0c;主要区别体现在定义、功能、灵活性、性能及适用场景等方面。以下是详细对比&#xff1a; 1. 定义与本质 IList 接口 抽象契约&#xff1a;仅定义集合的基本操作&#xff08;如索引访问、添加、…...

在 Azure OpenAI 上使用 Elastic 优化支出和内容审核

作者&#xff1a;来自 Elastic Muthukumar Paramasivam&#xff0c;Bahubali Shetti 及 Daniela Tzvetkova 我们为 Azure OpenAI 正式发布包添加了更多功能&#xff0c;现在提供内容过滤监控和计费见解的增强&#xff01; 在之前的博客中&#xff0c;我们展示了如何使用 Elasti…...

Redis学习专题(三)主从复制

目录 引言&#xff1a; 1、搭建一主多从 1) 创建/hspredis目录, 并拷贝redis.conf 到 /hspredis 2) vi /hspredis/redis.conf , 进行如下设置 3) 创建3个文件/hspredis/redis6379.conf 、/hspredis/redis6380.conf 、/hspredis/redis6381.conf 并编辑 4) 启动三台redis服…...

设计模式之备忘录模式

在日常开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;需要保存对象的某个历史状态&#xff0c;以便将来恢复。这种需求最常见的例子就是“撤销操作”。在这种情况下&#xff0c;备忘录模式(Memento Pattern)就派上了用场。 目录 1. 概念 2. 代码实现 3. 总结 1. …...

深度学习-runner.run(data_loaders, cfg.workflow)内部执行过程

文件&#xff1a;~/catkin_ws/SparseDrive/projects/mmdet3d_plugin/apis/mmdet_train.py 完成数据加载器、优化器、运行器实例化后&#xff0c; RUNNERS.register_module() class IterBasedRunner(BaseRunner):"""Iteration-based Runner.This runner train m…...

嵌入式开发学习日志(linux系统编程--文件读写函数)Day24

一、系统编程 标准oi 【输入输出】 stdio.h 头文件 &#xff1a;stdio.h >标准输入输出头文件&#xff1b;/usr/include/stdio.h 二、文件操作 1、关于文件操作的步骤 &#xff08;1&#xff09;打开文件&#xff1b; &#xff08;2&#xff09;io操作&#xff0c;读写…...

DEBUG:Lombok 失效

DEBUG&#xff1a;Lombok 失效 问题描述 基于 Spring Boot 的项目中&#xff0c;编译时显示找不到 log 属性。查看对应的 class 类&#xff0c;Lombok 正常在编译时生成 log 属性。 同时存在另一个问题&#xff0c;使用Getter注解&#xff0c;但实际使用中该注解并没有生效&…...

Qt 控件发展历程 + 目标(1)

文章目录 声明简述控件的发展历程学习目标QWidget属性 简介&#xff1a;这篇文章只是一个引子&#xff0c;介绍一点与控件相关的但不重要的内容&#xff08;浏览浏览即可&#xff09;&#xff0c;这一章节最为重要的还是要把之后常用且重要的控件属性和作用给学透&#xff0c;学…...

按键精灵ios/安卓辅助工具高级函数OcrEx文字识别(增强版)脚本开发介绍

函数名称 OcrEx文字识别&#xff08;增强版&#xff09; 函数功能 返回指定区域内所有识别到的字符串、左上角坐标、区域宽高、可信度&#xff0c;无需自制字库&#xff0c;识别范围越小&#xff0c;效率越高&#xff0c;结果越准确 注意&#xff1a;安卓版按键APP需在设置…...

零基础入门Selenium自动化测试:自动登录edu邮箱

&#x1f31f; Selenium简单概述一下 Selenium 是一个开源的自动化测试工具&#xff0c;主要用于 Web 应用程序的功能测试。它能够模拟用户操作浏览器的行为&#xff08;如点击按钮、填写表单、导航页面等&#xff09;&#xff0c;应用于前端开发、测试和运维领域。 特点 跨…...

MySQL高频面试八连问(附场景化解析)

文章目录 "为什么订单查询突然变慢了&#xff1f;"——从这个问题开始说起一、索引的生死时速&#xff08;必考题&#xff01;&#xff09;二、事务的"套娃"艺术三、锁机制的相爱相杀四、存储引擎的抉择五、慢查询的破案技巧六、分页的深度优化七、高可用架…...

JVM 性能问题排查实战10连击

&#x1f5c2;️ 目录 前言&#xff1a;理论掌握只是起点&#xff0c;定位能力才是核心全局排查模型&#xff1a;三步法1️⃣Full GC 频繁触发&#xff1a;老年代压力过大2️⃣ OOM 爆炸&#xff1a;元空间泄漏 or 缓存未清理3️⃣ CPU 飙升却不是 GC&#xff1a;线程阻塞或热方…...

零基础深入解析 ngx_http_session_log_module

一、引言 在传统的 HTTP 日志中&#xff0c;每个请求都会被单独记录&#xff0c;这对于短连接、异步加载等场景非常直观&#xff1b;但在一些需要以“会话”为单位分析用户行为的场景下&#xff0c;如视频点播、多资源并行加载、长轮询等&#xff0c;单个请求日志难以准确反映…...

10.17 LangChain v0.3核心机制解析:从工具调用到生产级优化的实战全指南

LangChain v0.3 技术生态与未来发展 关键词:LangChain 工具调用, 聊天模型集成, @tool 装饰器, ToolMessage 管理, 多模态交互 使用聊天模型实现工具调用 LangChain v0.3 通过 工具调用(Tool Calling) 机制,将大模型与外部工具深度结合,形成闭环能力链。本节以 GPT-4、L…...

Android Framework学习七:Handler、Looper、Message

文章目录 简介LooperMessageMessageQueueHandlerFramework学习系列文章 简介 Looper当做一台传送装置&#xff0c;MessageQueue是传送带&#xff0c;传送带上放的是Message&#xff0c;Handler用于发送Message分发与接收处理。 Looper frameworks/base/core/java/android/app…...

分钟级降水预报API:精准预测每一滴雨的智慧科技

引言&#xff1a;天气预报进入"分钟时代" 在数字化生活高度发达的今天&#xff0c;人们对天气预报的精确度要求越来越高。传统的24小时预报或小时级预报已无法满足出行、物流、户外活动等场景的精细化需求。分钟级降水预报API的出现&#xff0c;标志着气象服务正式进…...

民政部等部门针对老人权益保障工作发布指导意见

​ 1 品牌资讯 佛慈制药&#xff1a;将探索开发特医食品等产品 李子园将丰富大健康产品矩阵适应银发族需求 京东健康2025年第一季度收入166.45亿元 宁美浩维获融资&#xff0c;致力提供健康管理方案 2 行业动态 固生堂合作华为&#xff0c;联合推动中医药智慧化转型 怡…...

LinkedList源码分析

1. LinkedList初始化 public class LinkedListTest {public static void main(String[] args) {LinkedList<String> list new LinkedList<String>();// 新增list.add("a");list.add("b");list.add("c");list.add("d");l…...