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.mzph.cn/news/594767.shtml

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

相关文章

图形数据库

图形数据库是一种专门用于存储和查询图形数据结构的数据库系统。图形数据结构由节点&#xff08;顶点&#xff09;和边&#xff08;连接节点的关系&#xff09;组成&#xff0c;这种结构非常适合表示实体之间的复杂关系和网络。图形数据库的主要目标是提供高效的图形数据管理和…

C++上位软件通过LibModbus开源库和西门子S7-1200/S7-1500/S7-200 PLC进行ModbusTcp 和ModbusRTU 通信

前言 一直以来上位软件比如C等和西门子等其他品牌PLC之间的数据交换都是大家比较头疼的问题&#xff0c;尤其是C上位软件程序员。传统的方法一般有OPC、Socket 等&#xff0c;直到LibModbus 开源库出现后这种途径对程序袁来说又有了新的选择。 Modbus简介 Modbus特点 1 &#…

书生-浦路大模型全链路开源体系

2023年&#xff0c;大模型成为热门关键词 论文链接 大模型已经成为发展通用人工智能的重要途经 模型评测过程&#xff1a;从模型到应用 全链条开源开发体系 | 数据&#xff1a; 多模态融合 万卷包含文本、图像和视频等多模态数据&#xff0c;涵盖科技、文学、媒体、教育和法…

简单计算器实现,包括两个数

正在加载中... 简单计算器实现&#xff0c;包括两个数 ❤ 厾罗 简单计算器实现&#xff0c;包括两个数 以下代码用于实现简单计算器实现&#xff0c;包括两个数基本的加减乘除运算&#xff1a; 实例(Python 3.0) # Filename : test.py # author by : www.dida100.com …

SSD的控制器架构

SSD核心主要由3部分组成&#xff1a;主控&#xff0c;固件和闪存。 主控里面运行固件&#xff0c;固件对前后端数据进行处理并担负其他职能&#xff0c;包括磨损均衡&#xff0c;垃圾回收&#xff0c;内存映射&#xff0c;坏块管理等。 主控和固件就构成了SSD的控制器架构&am…

芯课堂 | MCU之EXT

概述 本文将介绍一下华芯微特MCU的嵌套向量中断控制器&#xff08;NVIC&#xff09;与外部中断/事件控制器&#xff08;EXTI&#xff09;的使用方法等。 01.嵌套向量中断控制器&#xff08;NVIC&#xff09; NVIC的全称是Nested vectoredinterrupt controller&#xff0c;即嵌…

普中STM32-PZ6806L开发板(HAL库函数实现-温度传感器DS18B20)

简介 主芯片STM32F103ZET6, 通过引脚PG11 连接DS18B20, 读取DS18B20采集的温度数据;电路原理图 DS18B20电路图 DS18B20 与 主芯片连接引脚 其他知识 DS18B20资料 DS18B20数据手册 DS18B20 简介 单线通讯的温度传感器, 测量温度在-55℃ 到 125℃&#xff0c; 在-10C 到…

平衡二叉树,力扣

目录 前序遍历与后续遍历 题目地址&#xff1a; 题目&#xff1a; 我们直接看题解吧&#xff1a; 审题目事例提示&#xff1a; 解题方法&#xff1a; 难度分析&#xff1a; 解题方法分析&#xff1a; 解题分析&#xff1a; 解题思路&#xff1a; 代码实现&#xff1a; 补充说明…

【每日论文阅读】生成模型篇

联邦多视图合成用于元宇宙 标题: Federated Multi-View Synthesizing for Metaverse 作者: Yiyu Guo; Zhijin Qin; Xiaoming Tao; Geoffrey Ye Li 摘要: 元宇宙有望提供沉浸式娱乐、教育和商务应用。然而&#xff0c;虚拟现实&#xff08;VR&#xff09;在无线网络上的传输是…

【UEFI基础】EDK网络框架(通用函数和数据)

通用函数和数据 DPC DPC全称Deferred Procedure Call。Deferred的意思是“延迟”&#xff0c;这个DPC的作用就是注册函数&#xff0c;然后在之后的某个时刻调用&#xff0c;所以确实是有“延迟”的意思。DPC在UEFI的实现中包括两个部分。一部分是库函数DxeDpcLib&#xff0c;…

Unity中使用四元数乘法表示旋转

四元数乘法旋转的本质是旋转的连续应用。当你执行p * q时&#xff0c;可以理解为首先应用四元数p的旋转&#xff0c;然后再应用四元数q的旋转。 四元数旋转乘法主要分为全局坐标的旋转和局部坐标的旋转. 全局坐标下的旋转&#xff1a; transform.rotationtransform.roation*…

考研复试英语口语问答举例第一弹

考研复试英语口语问答举例第一弹 文章目录 考研复试英语口语问答举例第一弹Question &#xff1a;介绍你的读研兴趣与动机Answer1&#xff1a;&#xff08;医疗与人工智能结合方向&#xff09;Answer2&#xff1a;&#xff08;分布式与网安方向&#xff09;Answer3&#xff1a;…

AUTOSAR规范与ECU软件开发(基础篇)1.2 汽车电子控制系统的基本构成

目录 前言 1、 传感器 2、 电子控制单元(ECU) 3、 执行器 前言 汽车电子控制系统主要由传感器(Sensor) 、 电子控制单元(Electronic Control Unit, ECU) 和执行器(Actuator) 组成(图1.1) ,对被控对象(Controlled Object) 进行控制。

斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3,

斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13,特别指出&#xff1a;第0项是0&#xff0c;第1项是第一个1。从第三项开始&#xff0c;每一项都等于前两项之和。 Python 实现斐波那契数列代码如下&#xff1a; 实例(Python 3.0) # -*- coding: UTF-8 -*- # File…

JS和TS的基础语法学习以及babel的基本使用

简介 本文主要介绍了一下js和ts的基础语法,为前端开发zuo JavaScript 更详细的 JavaScript 学习资料&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 简介 定位 : JavaScript 是一种动态语言&#xff0c;它包含类型、运算符、标准内置&#xff08; bu…

数字IC设计——数字电路基本元器件

现代数字集成电路基本由CMOS晶体管构成&#xff0c;而CMOS门电路由PMOS场效应管和NMOS场效应管以对称互补的形式组成&#xff0c;所谓“互补”&#xff0c;即利用互补型MOSFET&#xff0c;即pMOS和nMOS&#xff0c;二者成对出现构成互补电路。 这种电路具有高的电路可靠性和抗干…

Mysql show Profiles详解

1.简介 show profile 和 show profiles 命令用于展示SQL语句的资源使用情况&#xff0c;包括CPU的使用&#xff0c;CPU上下文切换&#xff0c;IO等待&#xff0c;内存使用等&#xff0c;这个命令对于分析某个SQL的性能瓶颈非常有帮助&#xff0c;借助于show profile的输出信息&…

力扣hot100 二叉树的直径

&#x1f468;‍&#x1f3eb; 题目地址 一个节点的最大直径 它左树的深度 它右树的深度 &#x1f60b; AC code /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* Tr…

(基础篇)go常用使用场景三(死锁的场景及处理)

目录 一、通道的发送和接收不匹配 二、没有足够的机会让 goroutine 执行完成 三、资源竞争

[Android]RadioButton控件

RadioButton控件 RadioButton控件是单选按钮控件&#xff0c;它继承自Button控件&#xff0c;可以直接使用Button控件支持的各种属性和方法。 与普通按钮不同的是&#xff0c;RadioButton控件多了一个可以选中的功能&#xff0c;能额外指定一个android&#xff1a;checked属性…