Unity DOTS中的baking(二)Baker的触发

Unity DOTS中的baking(二)Baker的触发

我们知道,当传入Baker的authoring component的值发生变化时,就会触发baking。不过在有些情况下,component所引用的对象没有变化,而是对象自身内部的一些属性发生了变化。这种情况下,是否会触发baking呢?我们来动手验证一下。

首先定义一个继承自ScriptableObject的ImageGeneratorInfo类:

[CreateAssetMenu(menuName = "ImageGeneratorInfo")]
public class ImageGeneratorInfo : ScriptableObject
{[Range(0.0f, 1.0f)]public float Spacing;public Mesh Mesh;public Material Material;
}

这样,我们就可以利用这个脚本创建asset了,随便给它塞点东西:

Unity DOTS中的baking(二)1

然后我们修改下baker脚本,在ECS Component中新增一个spacing,读取assets中的Spacing值:

public class MyAuthoring : MonoBehaviour
{public int bakeIntData = 0;public ImageGeneratorInfo info;class MyBaker : Baker<MyAuthoring>{public override void Bake(MyAuthoring authoring){Debug.Log("==========================Bake Invoked!========================== " + authoring.name);if(authoring.info == null) return;var entity = GetEntity(TransformUsageFlags.None);AddComponent(entity, new MyComponent {value = authoring.bakeIntData,spacing = authoring.info.Spacing});}}
}public struct MyComponent : IComponentData
{public int value;public float spacing;
}

我们在SubScene中新建一个空的GameObject,挂上MyAuthoring脚本,并在Info中设置上刚刚创建的ImageGeneratorInfo asset,随即就会触发baking:

Unity DOTS中的baking(二)2

Unity DOTS中的baking(二)3

此时,如果直接去修改asset本身,例如将asset的spacing参数设置为其他的值,并不会触发baking。这就意味着,转换后的Entity所拥有的MyComponent数据是错误的。对此,Unity官方文档给出了解释:

However, Unity doesn’t automatically track data from other sources, such as authoring components or assets. You need to add a dependency to the baker so it can track this kind of data. To do this, use the methods that the Baker class provides to access other components and GameObjects instead of the methods provided by the GameObject:

Unity只会自动地追踪依赖authoring component自身的变化,至于component所引用的资源,则需要使用DependsOn来显式定义依赖:

public override void Bake(MyAuthoring authoring)
{Debug.Log("==========================Bake Invoked!========================== " + authoring.name);DependsOn(authoring.info);if(authoring.info == null) return;var entity = GetEntity(TransformUsageFlags.None);AddComponent(entity, new MyComponent {value = authoring.bakeIntData,spacing = authoring.info.Spacing});
}

此时再修改Spacing参数,就会触发baking了:

Unity DOTS中的baking(二)4

这个DependsOn背后究竟做了什么呢?查看一下DependsOn的源码:

/// <summary>
/// This will take a dependency on Object of type T.
/// </summary>
/// <param name="dependency">The Object to take a dependency on.</param>
/// <typeparam name="T">The type of the object. Must be derived from UnityEngine.Object.</typeparam>
/// <returns>The Object of type T if a dependency was taken, null otherwise.</returns>
public T DependsOn<T>(T dependency) where T : UnityEngine.Object
{_State.Dependencies->DependResolveReference(_State.AuthoringSource.GetInstanceID(), dependency);// Transform component takes an implicit dependency on the entire parent hierarchy// since transform.position and friends returns a value calculated from all parentsvar transform = dependency as Transform;if (transform != null)_State.Dependencies->DependOnParentTransformHierarchy(transform);return dependency;
}

这里最关键的函数就是这个DependResolveReference了,再来看下它的源码:

public void DependResolveReference(int authoringComponent, UnityEngine.Object referencedObject)
{// Tricky unity details ahead:// A UnityEngine.Object might be//  - actual null (ReferenceEquals(referencedObject, null)  -> instance ID zero)//  - currently unavailable (referencedObject == null)//      - If it is unavailable, it might still have an instanceID. The object might for example be brought back through undo or putting the asset in the same path / guid in the project//        In that case it will be re-established with the same instanceID and hence we need to have a dependency on when an object//        that previously didn't exist now starts existing at the instanceID that previously mapped to an invalid object.//  - valid (referencedObject != null) (instanceID non-zero)var referencedInstanceID = ReferenceEquals(referencedObject, null) ? 0 : referencedObject.GetInstanceID();if (referencedInstanceID != 0){AddObjectReference(referencedInstanceID);var obj = Resources.InstanceIDToObject(referencedInstanceID);var objTypeId = TypeManager.GetTypeIndex(referencedObject.GetType());AddObjectExist(new ObjectExistDependency { InstanceID = referencedInstanceID, exists = (obj != null), Type = objTypeId });#if UNITY_EDITOR//@todo: How do we handle creation / destruction of assets / components?if (EditorUtility.IsPersistent(referencedObject))AddPersistentAsset(referencedObject.GetInstanceID());
#endif}
}

从源码的注释得知,Unity需要处理依赖的object是fake null(例如object引用为missing)的情况。ObjectExistDependency这个类记录了所引用的object对应的asset是否存在。当调用DependsOn时,就会新建一个该类的对象保存起来。之后,再检测到asset变化时,会触发CalculateObjectExistDiffsJob这个job。该job会获取记录的所有objects对应的asset当前存在的状态,与之前ObjectExistDependency保存时的状态进行比较,如果不一致,说明asset发生了变化(从无到有或者从有到无),需要重新进行baking:

// Resolve the objectIds (Get Object)
// Check if they are null (If they are null)
NativeArray<bool> objectExists = new NativeArray<bool>(objectIds.Length, Allocator.TempJob);InstanceIDsToValidArrayMarker.Begin();
Resources.InstanceIDsToValidArray(objectIds.AsArray(), objectExists);
InstanceIDsToValidArrayMarker.End();var diffJob = new CalculateObjectExistDiffsJob()
{objectExistDependencies = _StructuralObjectExistDependency,objectExists = objectExists,deduplicatedObjIds = deduplicatedObjIds,changedComponentsPerThread = changedComponentsPerThread
};
var diffJobHandle = diffJob.Schedule(DependenciesHashMapHelper.GetBucketSize(_StructuralObjectExistDependency), 64);

传入job的四个参数,objectExistDependencies表示之前记录的dependency,objectExists就是当前asset的状态,deduplicatedObjIds代表去重过的object instance id,changedComponentsPerThread顾名思义就是记录需要重新baking的authoring component。job具体执行的代码如下:

public void ProcessEntry(int threadIndex, in UnsafeParallelMultiHashMap<int, ObjectExistDependency> hashMap, in int key, in ObjectExistDependency value)
{// Add them if the exist state has changed (State has changed)int existsID = deduplicatedObjIds[value.InstanceID];if (value.exists != objectExists[existsID]){changedComponentsPerThread.Add(key, m_ThreadIndex);IncrementalBakingLog.RecordComponentBake(key, ComponentBakeReason.ObjectExistStructuralChange, value.InstanceID, value.Type);}
}

传入的key和value取自于objectExistDependencies这个数据结构,分别表示authoring component的instance id,以及记录的ObjectExistDependency对象。在解释完这些数据结构的含义之后,这里job执行的逻辑就很清晰了。

哎,等一下,这里到目前为止所说的,都是关于asset是否存在的依赖,那么asset本身是否修改,这里的依赖是在哪里注册的呢?实际上,还是在这个DependResolveReference函数里:

AddObjectReference(referencedInstanceID);

这个函数会把authoring component和所依赖的object instance id关联,最终保存到一个UnsafeParallelMultiHashMap<int, int>类型的_PropertyChangeDependency变量里。然后,当components,gameobjects,或是assets发生变化时,都会触发相应的job,对该dependency进行扫描。

JobHandle nonStructuralChangedComponentJobHandle = default;
if (incrementalConversionDataCache.ChangedComponents.Length > 0)
{var nonStructuralChangedComponentJob = new NonStructuralChangedComponentJob(){changedComponents = incrementalConversionDataCache.ChangedComponents,reversePropertyChangeDependency = _ReversePropertyChangeDependency,changedComponentsPerThread = changedComponentsPerThread};nonStructuralChangedComponentJobHandle = nonStructuralChangedComponentJob.Schedule(incrementalConversionDataCache.ChangedComponents.Length, 64, calculateGlobalReversePropertyJobHandle);
}JobHandle nonStructuralChangedGameObjectPropertiesJobHandle = default;
if (incrementalConversionDataCache.ChangedGameObjectProperties.Length > 0)
{var nonStructuralChangedGameObjectPropertiesJob = new NonStructuralChangedGameObjectPropertiesJob(){changedGameObjects = incrementalConversionDataCache.ChangedGameObjectProperties,reversePropertyChangeDependency = _ReversePropertyChangeDependency,reverseGameObjectPropertyChangeDependency = _ReverseObjectPropertyDependency,changedComponentsPerThread = changedComponentsPerThread};nonStructuralChangedGameObjectPropertiesJobHandle = nonStructuralChangedGameObjectPropertiesJob.Schedule(incrementalConversionDataCache.ChangedGameObjectProperties.Length, 64, JobHandle.CombineDependencies(calculateGameObjectPropertyReverseJobHandle, calculateGlobalReversePropertyJobHandle));
}JobHandle nonStructuralChangedAssetsJobHandle = default;
#if UNITY_EDITOR
if (incrementalConversionDataCache.ChangedAssets.Length > 0)
{var nonStructuralChangedAssetsJob = new NonStructuralChangedAssetsJob(){changedAssets = incrementalConversionDataCache.ChangedAssets,reversePropertyChangeDependency = _ReversePropertyChangeDependency,changedComponentsPerThread = changedComponentsPerThread};nonStructuralChangedAssetsJobHandle = nonStructuralChangedAssetsJob.Schedule(incrementalConversionDataCache.ChangedAssets.Length, 64, calculateGlobalReversePropertyJobHandle);
}
#endif

NonStructuralChangedComponentJob这个job为例:

public void Execute(int i)
{var component = changedComponents[i];changedComponentsPerThread.Add(component.instanceID, m_ThreadIndex);IncrementalBakingLog.RecordComponentBake(component.instanceID, ComponentBakeReason.ComponentChanged, component.instanceID, component.unityTypeIndex);IncrementalBakingLog.RecordComponentChanged(component.instanceID);foreach (var dep in reversePropertyChangeDependency.GetValuesForKey(component.instanceID)){changedComponentsPerThread.Add(dep, m_ThreadIndex);IncrementalBakingLog.RecordComponentBake(dep, ComponentBakeReason.GetComponentChanged, component.instanceID, component.unityTypeIndex);}
}

首先,肯定是将change component自身记录进来,它是需要重新baking的;然后,对当前记录的dependency进行扫描,取出依赖该change component的authoring components,它可能存在多个,需要进行遍历,一一记录。这样,所有依赖change component的autoring components全部都会重新baking。

此外,我们的authoring component也有可能需要获取自身GameObject上的某些component,例如我们在ECS Component中新增一个float3的position,读取transform上的position:

public override void Bake(MyAuthoring authoring)
{Debug.Log("==========================Bake Invoked!========================== " + authoring.name);DependsOn(authoring.info);if(authoring.info == null) return;var transform = authoring.transform;var entity = GetEntity(TransformUsageFlags.None);AddComponent(entity, new MyComponent {value = authoring.bakeIntData,spacing = authoring.info.Spacing,position = transform.position});
}public struct MyComponent : IComponentData
{public int value;public float spacing;public float3 position;
}

然而,直接使用authoring.transform获取到的transform,如果发生了变化,并不会重新触发baking。也就是说我们也需要手动指定依赖。Unity官方推荐使用GetComponent函数来获取component。

It is important to access other authoring components by using the GetComponent methods, because doing so registers dependencies. If we would have used authoring.transform here instead, a dependency would not have been registered, and moving the authoring GameObject while live baking would not rerun the baker as it should.

那我们就换用GetComponent<Transform>()来测试一下:

Unity DOTS中的baking(二)5

的确修改transform就会触发baking了。秉着知其然知其所以然的精神,我们来看看GetComponent背后的代码:

/// <summary>
/// Retrieves the component of Type T in the GameObject
/// </summary>
/// <param name="gameObject">The GameObject to get the component from</param>
/// <typeparam name="T">The type of component to retrieve</typeparam>
/// <returns>The component if a component matching the type is found, null otherwise</returns>
/// <remarks>This will take a dependency on the component</remarks>
private T GetComponentInternal<T>(GameObject gameObject) where T : Component
{var hasComponent = gameObject.TryGetComponent<T>(out var returnedComponent);_State.Dependencies->DependOnGetComponent(gameObject.GetInstanceID(), TypeManager.GetTypeIndex<T>(), hasComponent ? returnedComponent.GetInstanceID() : 0, BakeDependencies.GetComponentDependencyType.GetComponent);// Transform component takes an implicit dependency on the entire parent hierarchy// since transform.position and friends returns a value calculated from all parentsvar transform = returnedComponent as Transform;if (transform != null)_State.Dependencies->DependOnParentTransformHierarchy(transform);return returnedComponent;
}

不难发现关键代码就在DependOnGetComponent这个函数了:

public void DependOnGetComponent(int gameObject, TypeIndex type, int returnedComponent, GetComponentDependencyType dependencyType)
{if (returnedComponent != 0)AddObjectReference(returnedComponent);AddGetComponent(new GetComponentDependency {GameObject = gameObject, Type = type, ResultComponent = returnedComponent, DependencyType = dependencyType});
}

returnedComponent代表获取到的component的instance id,如果component存在,就需要追踪它本身是否发生变化,因此这里就和前面一样,使用AddObjectReference这个函数记录依赖。AddGetComponent会把要获取的GameObject,component,component的type index,以及依赖类型给记录下来,最终保存到_StructuralGetComponentDependency这个hashmap中。CalculateStructuralGetComponentDependencyJob会用到保存的数据,当检测到components,gameobjects,或是assets变化时,job就会触发。

public void ProcessEntry(int threadIndex, in UnsafeParallelMultiHashMap<int, GetComponentDependency> hashMap, in int key, in GetComponentDependency value)
{if (!value.IsValid(ref components, ref hierarchy)){changedComponentsPerThread.Add(key, m_ThreadIndex);IncrementalBakingLog.RecordComponentBake(key, ComponentBakeReason.GetComponentStructuralChange, value.ResultComponent, value.Type);}
}

从字面含义来看,只有GetComponentDependency类型的value判断为invalid时,才会把对应的key标记为需要重新baking。这里的key和value就是hashmap中的键值对,key表示authoring component,value里记录了当时使用GetComponent获取到的component依赖。那么,什么时候IsValid返回的是false呢?

public bool IsValid(ref GameObjectComponents components, ref SceneHierarchy hierarchy)
{switch (DependencyType){case GetComponentDependencyType.GetComponentInParent:return GameObjectComponents.GetComponentInParent(ref components, ref hierarchy, GameObject, Type) == ResultComponent;case GetComponentDependencyType.GetComponent:return components.GetComponent(GameObject, Type) == ResultComponent;case GetComponentDependencyType.GetComponentInChildren:return GameObjectComponents.GetComponentInChildren(ref components, ref hierarchy, GameObject, Type) == ResultComponent;}return false;
}

这里的DependencyType为GetComponentDependencyType.GetComponent,可以看到这里会再去获取一下记录的GameObject身上最新的component,如果和之前记录的component instance id不同,说明component经历了从无到有或者从有到无的过程,它已经不是之前的那个对象了,此时就会返回false。总结一下,GetComponentInternal函数,不仅对component内部的值发生变化进行了依赖注册,还对component自身引用发生变化也进行了依赖注册。

如果DependsOn和GetComponent所依赖的component,它是个transform,Unity还会调用DependOnParentTransformHierarchy这个函数做一些额外的依赖工作。这是因为transform本身就不是独立的,它依赖整个parent hierarchy。这个事情是必要的,比如我们给当前挂有MyAuthoring脚本的GameObject建立一个父节点,如果父节点的transform发生变化,或者层级关系发生了变化,那么子节点的world transform必然也发生了变化,理应就要再次触发baking。

Unity DOTS中的baking(二)6

DependOnParentTransformHierarchy的实现如下:

public void DependOnParentTransformHierarchy(Transform transform)
{if (transform != null){var hashGenerator = new xxHash3.StreamingState(false);GameObject go = transform.gameObject;int goInstanceID = go.GetInstanceID();// We take the dependency on the parent hierarchy.transform = transform.parent;while (transform != null){hashGenerator.Update(transform.gameObject.GetInstanceID());AddObjectReference(transform.GetInstanceID());transform = transform.parent;}var hash = new Hash128(hashGenerator.DigestHash128());AddGetHierarchy(new GetHierarchyDependency {GameObject = goInstanceID, Hash = hash, DependencyType = GetHierarchyDependencyType.Parent});}
}

可以看到,Unity会对当前节点到根节点整个parent hierarchy进行扫描,对每个父节点的transform都建立了依赖关系,然后还记录了每个父节点GameObject的instance id,把它们汇总在一起生成一个128位的hash值。这个hash值就是用来标记当前的hierarchy。值得一提的是,Unity ECS 1.0.16版本之前的这段代码是有问题的,之前的版本也会把当前节点的instance id也统计进去,这点在Unity的changelog里有说明:

Changelog

[1.0.16] - 2023-09-11

Fixed

  • Fixed a hash mismatch on DependOnParentTransformHierarchy

之后,CalculateStructuralGetHierarchyDependencyJob这个job会使用之前记录的GetHierarchyDependency,判断hierarchy是否发生了变化(如果是1.0.16之前的版本,这里就必然会发生变化了,两边计算的方式压根就不一致)

public void ProcessEntry(int threadIndex, in UnsafeParallelMultiHashMap<int, GetHierarchyDependency> hashMap, in int key, in GetHierarchyDependency value)
{if (!value.IsValid(ref hierarchy)){changedComponentsPerThread.Add(key, m_ThreadIndex);IncrementalBakingLog.RecordComponentBake(key, ComponentBakeReason.GetHierarchyStructuralChange, 0, default);}
}

具体的判断逻辑位于IsValid

public Hash128 GetParentsHash(ref SceneHierarchy hierarchy, int instanceId)
{var hashGenerator = new xxHash3.StreamingState(false);if (hierarchy.TryGetIndexForInstanceId(instanceId, out int currentIndex)){while (currentIndex != -1){int parentIndex = hierarchy.GetParentForIndex(currentIndex);if (parentIndex != -1){int parentInstanceID = hierarchy.GetInstanceIdForIndex(parentIndex);hashGenerator.Update(parentInstanceID);}currentIndex = parentIndex;}}return new Hash128(hashGenerator.DigestHash128());
}public bool IsValid(ref SceneHierarchy hierarchy)
{Hash128 returnValue = default;switch (DependencyType){case GetHierarchyDependencyType.Parent:returnValue = GetParentsHash(ref hierarchy, GameObject);break;case GetHierarchyDependencyType.ImmediateChildren:returnValue = GetChildrenHash(ref hierarchy, GameObject, false);break;case GetHierarchyDependencyType.AllChildren:returnValue = GetChildrenHash(ref hierarchy, GameObject, true);break;}return (returnValue == Hash);
}

DependOnParentTransformHierarchy注册的dependency type为GetHierarchyDependencyType.Parent,因此这里计算出的hash值表示当前的parent hierarchy。如果两个hash值不同,说明两个hierarchy不同,那么authoring component需要重新运行baking。而且xxhash算法是一种非常快速的非加密哈希算法,它基本不可能发生碰撞,所以这里如果两个hash值相等,就可以认为两个hierarchy是完全相同的。

最后,我们来实际演示一下:

Unity DOTS中的baking(二)7

Reference

[1] Baker overview

[2] EntityComponentSystemSamples

[3] xxHash

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/230486.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编写.NET的Dockerfile文件构建镜像

创建一个WebApi项目&#xff0c;并且创建一个Dockerfile空文件&#xff0c;添加以下代码&#xff0c;7.0代表的你项目使用的SDK的版本&#xff0c;构建的时候也需要选择好指定的镜像tag FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443F…

深度学习基础知识神经网络

神经网络 1. 感知机 感知机&#xff08;Perceptron&#xff09;是 Frank Rosenblatt 在1957年提出的概念&#xff0c;其结构与MP模型类似&#xff0c;一般被视为最简单的人工神经网络&#xff0c;也作为二元线性分类器被广泛使用。通常情况下指单层的人工神经网络&#xff0c…

超实用的小红书达人投放策略分析,纯干货

为什么我投放了小红书达人却没有什么效果&#xff1f; 品牌到底应该怎么投放小红书达人&#xff1f; 品牌小红书达人投放怎么去把控和规划&#xff1f; 小红书达人作为品牌方和用户之间的桥梁&#xff0c;直接影响消费决策。达人粉丝数量大&#xff0c;粘性高&#xff0c;很…

一加 Buds 3正式发布:普及旗舰音质 一加用户首选

1月4日&#xff0c;一加新品发布会正式推出旗下新款耳机一加 Buds 3。延续一加经典美学&#xff0c;秉承音质完美主义追求&#xff0c;一加 Buds 3全面普及一加旗舰耳机体验&#xff0c;其搭载旗舰同款“超清晰同轴双单元”&#xff0c;配备49dB 4000Hz超宽频主动降噪&#xff…

Oracle笔记-查看表已使用空间最大空间

目前以Oracle18c为例&#xff0c;主要是查这个表USER_SEGMENTS。 在 Oracle 18c 数据库中&#xff0c;USER_SEGMENTS 是一个系统表&#xff0c;用于存储当前用户&#xff08;当前会话&#xff09;拥有的所有段的信息。段是 Oracle 中分配存储空间的逻辑单位&#xff0c;用于存…

linux centos 添加临时ip

### 1.添加ip ip addr add IP/mask dev 网络设备 例&#xff1a;ip addr add 172.104.210.247/24 dev ens5f1 ### 2.启动网卡 ip link set up 网络设备 例&#xff1a;ip link set up ens3f0 ### 3.设置默认路由 ip route add default via GATEWAY 例&#xff1a;ip route add …

vu3-14

第一个需求是在用户登录成功之后&#xff0c;在主页显示用户的真实姓名和性别&#xff0c;这些信息要调用后端API获取数据库里面的信息&#xff0c;第二个需求是点击菜单1&#xff0c;在表单中修改用户信息之后&#xff0c;更新到后端数据库&#xff0c;然后在主页同步更新用户…

增删改查语句实现了解不同的函数与特殊字符unionunion all区别

一、crud&#xff08;增删改查&#xff09; 1.1、查询 概念&#xff1a; 查询数据是指从数据库中根据需求&#xff0c;使用不同的查询方式来获取不同的数据&#xff0c;是使用频率最高、最重要的操作 注&#xff1a;在MySQL中&#xff0c;当执行一条SQL语句后&#xff0c;系…

Docker中的核心概念

1.镜像 Image 一个镜像就代表一个软件。mysql镜像、redis镜像、mq镜像 2.容器 Container 一个镜像运行一次就会生成一个容器&#xff0c;容器就是一个运行的软件服务。 3.远程仓库 Repository 远程仓库用来存储所有软件的镜像&#xff0c;Docker Hub 4.本地仓库 用来存储…

Mybatis源码基本原理--XML版

文章目录 mybatis是什么架构设计首先建立起Mapper的代理工程和代理映射器的注册和使用XML文件解析数据源解析、创建和使用SQL执行器&#xff08;Executor&#xff09;的定义与实现SQL解析参数处理器&#xff1a;策略模式实现封装处理结果注解 mybatis 是什么 MyBatis 是一款优…

acwing 1358. 约数个数和(莫比乌斯函数)

设 d(x)&#xfffd;(&#xfffd;) 为 x&#xfffd; 的约数个数&#xff0c;给定 N,M&#xfffd;,&#xfffd;&#xff0c;求 ∑i1N∑j1Md(ij)∑&#xfffd;1&#xfffd;∑&#xfffd;1&#xfffd;&#xfffd;(&#xfffd;&#xfffd;) 输入格式 输入多组测试数据…

Java 读取超大excel文件

注意&#xff1a;此参考解决方案只是针对xlsx格式的excel文件&#xff01; Maven <dependency><groupId>com.monitorjbl</groupId><artifactId>xlsx-streamer</artifactId><version>2.2.0</version> </dependency>读取方式1…

SpringBoot的测试

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

基于Segformer实现PCB缺陷检测(步骤 + 代码)

导 读 本文主要介绍基于Segformer实现PCB缺陷检测 &#xff0c;并给出步骤和代码。 背景介绍 PCB缺陷检测是电子制造的一个重要方面。利用Segformer等先进模型不仅可以提高准确性&#xff0c;还可以大大减少检测时间。传统方法涉及手动检查&#xff0c;无法扩展且容易出错…

目标检测-Two Stage-Mask RCNN

文章目录 前言一、Mask RCNN的网络结构和流程二、Mask RCNN的创新点总结 前言 前文目标检测-Two Stage-Faster RCNN提到了Faster RCNN主要缺点是&#xff1a; ROI Pooling有两次量化操作&#xff0c;会引入误差影响精度 Mask RCNN针对这一缺点做了改进&#xff0c;此外Mask …

C/C++动态内存分配 malloc、new、vector(简单讲述)

路虽远&#xff0c;行则将至 事虽难&#xff0c;做则必成 今天来主要讲C中动态内存分配 其中会穿插一些C的内容以及两者的比较 如果对C语言中的动态内存分配还不够理解的同学 可以看看我之前的博客:C语言动态分配 在讲解C的动态内存分配之前 我们先讲一下C内存模型 &#xff1…

楼宇智慧能源消耗监测管理系统,楼宇中的能源“管家”

随着人口的增加&#xff0c;楼宇数据呈上涨趋势&#xff0c;但是楼宇智能建设在我国普及性远远不足&#xff0c;相比传统楼宇控制&#xff0c;智能楼宇控制系统对于楼宇内部的用电设备控制&#xff0c;能够更加的节约能源&#xff0c;降低成本。对于现代化楼宇而言&#xff0c;…

ORACLE P6 v23.12 最新虚拟机(VM)全套系统环境分享

引言 根据上周的计划&#xff0c;我简单制作了两套基于ORACLE Primavera P6 最新发布的23.12版本预构建了虚拟机环境&#xff0c;里面包含了全套P6 最新版应用服务 此虚拟机仅用于演示、培训和测试目的。如您在生产环境中使用此虚拟机&#xff0c;请先与Oracle Primavera销售代…

C#最佳工具集合:IDE、分析、自动化工具等

C#是企业中广泛使用的编程语言&#xff0c;特别是那些依赖微软的程序语言。如果您使用C#构建应用程序&#xff0c;则最有可能使用Visual Studio&#xff0c;并且已经寻找了一些扩展来对您的开发进行管理。但是&#xff0c;这个工具列表可能会改变您编写C#代码的方式。 C#编程的…

模拟器怎么代理IP?代理IP对手机设置模拟器有哪些影响?

一、代理IP的基本概念和作用流冠代理IP是一种网络服务&#xff0c;可以帮助用户隐藏自己的真实IP地址&#xff0c;通过代理服务器进行网络请求&#xff0c;从而保护用户的隐私和安全。在模拟器中&#xff0c;代理IP的作用也是如此&#xff0c;可以帮助模拟器隐藏真实的IP地址&a…