Unity DOTS中的Archetype与Chunk

Unity DOTS中的Archetype与Chunk

在Unity中,archetype(原型)用来表示一个world里具有相同component类型组合的entity。也就是说,相同component类型的entity在Unity内部会存储到一起,共享同一个archetype。

Unity DOTS中的Archetype与Chunk1

使用这样的设计,可以高效地通过component类型进行查询。例如,如果想查找所有具有component类型 A 和 B 的entity,那么只需找到表示这两个component的所有archetype,这比扫描所有entity更高效。

entity和componenet并不直接存储在archetype中,而是存储在称为chunk的连续内存块中。每个块由16KB组成,它们实际可以存储的entity数量取决于chunk所属archetype中component的数量和大小。 这样做可以更好匹配缓存行,同时也能更好支持并行操作。

在这里插入图片描述

对于一个chunk,它除了包含用于每种component类型的数组之外,还包含一个额外的数组来存储entity的ID。例如,在具有component类型 A 和 B 的archetype中,每个chunk都有三个数组:一个用于component A的数组,一个用于component B的数组,以及一个用于entity ID的数组。

Unity DOTS中的Archetype与Chunk3

chunk中的数据是紧密连续的,chunk的每个entity都存储在每个数组连续的索引中。添加一个新的entity时,会将其存储在第一个可用索引中。而当从chunk中移除entity时,chunk的最后一个entity将被移动以填补空隙。添加entity时,如果对应archetype的所有chunk都已满,则Unity将创建一个全新的chunk。类似地,当从chunk中移除最后一个entity时,Unity会将该chunk进行销毁。

在编辑器中,Unity提供了Archetypes窗口,可以方便预览当前所有的archetype。我们先用代码创建4个entity,然后给它们分别添加两种component:

public partial struct FirstSystem : ISystem
{
    public void OnCreate(ref SystemState state) 
    { 
        Entity e1 = state.EntityManager.CreateEntity();
        Entity e2 = state.EntityManager.CreateEntity();
        Entity e3 = state.EntityManager.CreateEntity();
        Entity e4 = state.EntityManager.CreateEntity();
        state.EntityManager.AddComponentData(e1, new FirstComponent { Value = 1 });
        state.EntityManager.AddComponentData(e2, new FirstComponent { Value = 2 });

        state.EntityManager.AddComponentData(e3, new FirstComponent { Value = 3 });
        state.EntityManager.AddComponentData(e3, new SecondComponent { Value = 13 });

        state.EntityManager.AddComponentData(e4, new FirstComponent { Value = 4 });
        state.EntityManager.AddComponentData(e4, new SecondComponent { Value = 14 });
    }
}

public struct FirstComponent : IComponentData
{
    public int Value;
}

public struct SecondComponent : IComponentData
{
    public int Value;
}

那么,根据之前的分析,e1和e2应该属于同一个archetype,而e3和e4则属于另一个archetype。在编辑器中运行,观察Archetypes窗口,可以发现的确如此:

Unity DOTS中的Archetype与Chunk4

Unity DOTS中的Archetype与Chunk5

根据官网的解释,Archetypes窗口中可以观察到以下信息:

PropertyDescription
Archetype nameThe archetype name is its hash, which you can use to find the archetype again across future Unity sessions.
EntitiesNumber of entities within the selected archetype.
Unused EntitiesThe total number of entities that can fit into all available chunks for the selected Archetype, minus the number of active entities (represented by the entities stat).
ChunksNumber of chunks this archetype uses.
Chunk CapacityThe number of entities with this archetype that can fit into a chunk. This number is equal to the total number of Entities and Unused Entities.
ComponentsDisplays the total number of components in the archetype and the total amount of memory assigned to them in KB. To see the list of components and their individual memory allocation, expand this section.
External ComponentsLists the Chunk components and Shared components that affect this archetype.

自此,我们对Unity的archetype和chunk有了基本的认识。接下来我们来看看源代码,深入一下细节部分。

在上面的例子中,我们使用EntityManager.CreateEntity这个函数来创建entity。函数内部会在创建entity之前,检查是否存在相应的archetype。由于此时尚不存在表示一个不含任何component类型的空entity的archetype,那么函数会先去创建archetype:

internal EntityArchetype CreateArchetype(ComponentType* types, int count, bool addSimulateComponentIfMissing)
{
    Assert.IsTrue(types != null || count == 0);
    EntityComponentStore->AssertCanCreateArchetype(types, count);

    ComponentTypeInArchetype* typesInArchetype = stackalloc ComponentTypeInArchetype[count + 2];

    var cachedComponentCount = FillSortedArchetypeArray(typesInArchetype, types, count, addSimulateComponentIfMissing);

    return CreateArchetype_Sorted(typesInArchetype, cachedComponentCount);
}

每个空的entity都要额外添加一个Simulate类型的component,这也是上面的截图中为何会多出一个component的原因。Unity首先会尝试从已有的空闲archetypes中寻找符合条件的archetype,没找到才会去真正创建一个。

public Archetype* GetOrCreateArchetype(ComponentTypeInArchetype* inTypesSorted, int count)
{
    var srcArchetype = GetExistingArchetype(inTypesSorted, count);
    if (srcArchetype != null)
        return srcArchetype;

    srcArchetype = CreateArchetype(inTypesSorted, count);

    return srcArchetype;
}

Unity的Archetype是一个非常复杂的struct,它记录了所代表的component信息,以及chunk信息,还有它们当前的状态:

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct Archetype
{
    public ArchetypeChunkData Chunks;
    public UnsafeList<ChunkIndex> ChunksWithEmptySlots;

    public ChunkListMap FreeChunksBySharedComponents;
    public ComponentTypeInArchetype* Types; // Array with TypeCount elements
    public int* EnableableTypeIndexInArchetype; // Array with EnableableTypesCount elements

    // back pointer to EntityQueryData(s), used for chunk list caching
    public UnsafeList<IntPtr> MatchingQueryData;

    // single-linked list used for invalidating chunk list caches
    public Archetype* NextChangedArchetype;

    public int EntityCount;
    public int ChunkCapacity;

    public int TypesCount;
    public int EnableableTypesCount;
    public int InstanceSize;
    public int InstanceSizeWithOverhead;
    public int ScalarEntityPatchCount;
    public int BufferEntityPatchCount;
    public ulong StableHash;
    public ulong BloomFilterMask;

    // The order that per-component-type data is stored in memory within an archetype does not necessarily match
    // the order that types are stored in the Types/Offsets/SizeOfs/etc. arrays. The memory order of types is stable across
    // runs; the Types array is sorted by TypeIndex, and the TypeIndex <-> ComponentType mapping is *not* guaranteed to
    // be stable across runs.
    // These two arrays each have TypeCount elements, and are used to convert between these two orderings.
    // - MemoryOrderIndex is the order an archetype's component data is actually stored in memory. This is stable across runs.
    // - IndexInArchetype is the order that types appear in the archetype->Types[] array. This is *not* necessarily stable across runs.
    //   (also called IndexInTypeArray in some APIs)
    public int* TypeMemoryOrderIndexToIndexInArchetype; // The Nth element is the IndexInArchetype of the type with MemoryOrderIndex=N
    public int* TypeIndexInArchetypeToMemoryOrderIndex; // The Nth element is the MemoryOrderIndex of the type with IndexInArchetype=N

    // These arrays each have TypeCount elements, ordered by IndexInArchetype (the same order as the Types array)
    public int*    Offsets; // Byte offset of each component type's data within this archetype's chunk buffer.
    public ushort* SizeOfs; // Size in bytes of each component type
    public int*    BufferCapacities; // For IBufferElementData components, the buffer capacity of each component. Not meaningful for non-buffer components.

    // Order of components in the types array is always:
    // Entity, native component data, buffer components, managed component data, tag component, shared components, chunk components
    public short FirstBufferComponent;
    public short FirstManagedComponent;
    public short FirstTagComponent;
    public short FirstSharedComponent;
    public short FirstChunkComponent;

    public ArchetypeFlags Flags;

    public Archetype* CopyArchetype; // Removes cleanup components
    public Archetype* InstantiateArchetype; // Removes cleanup components & prefabs
    public Archetype* CleanupResidueArchetype;
    public Archetype* MetaChunkArchetype;

    public EntityRemapUtility.EntityPatchInfo* ScalarEntityPatches;
    public EntityRemapUtility.BufferEntityPatchInfo* BufferEntityPatches;

    // @macton Temporarily store back reference to EntityComponentStore.
    // - In order to remove this we need to sever the connection to ManagedChangesTracker
    //   when structural changes occur.
    public EntityComponentStore* EntityComponentStore;

    public fixed byte QueryMaskArray[128];
}

其中,TypesCount表示archetype所保存的数据种类,一般就是entity+component类型数量,这里空entity自带一个Simulate的component,那么TypesCount即为2;Types就是对应的类型数组了;EntityCount为当前archetype所拥有的entity数量;ChunksWithEmptySlots表示archetype中所有空闲chunk的索引列表,archetype就是通过它来为entity指定chunk的;然后,一系列First打头的Component(FirstBufferComponentFirstManagedComponentFirstTagComponentFirstSharedComponentFirstChunkComponent)表示在type数组中第一个出现该类型的索引;SizeOfs是一个比较重要的字段,它表示每个component type所占的内存大小,通过它可以进而计算出archetype最多可拥有entity的数量,也就是ChunkCapacity字段;在此基础上,还可以计算出每个component type在chunk中的起始位置Offsets;最后,Chunks是一个ArchetypeChunkData类型的字段,它用来记录archetype中所有chunk的状态。

有了archetype之后,就可以进入创建entity的阶段了,那么下一步,Unity会先查找是否有可用的chunk,没有的话就得真正分配内存,去创建一个:

ChunkIndex GetChunkWithEmptySlots(ref ArchetypeChunkFilter archetypeChunkFilter)
{
    var archetype = archetypeChunkFilter.Archetype;
    fixed(int* sharedComponentValues = archetypeChunkFilter.SharedComponentValues)
    {
        var chunk = archetype->GetExistingChunkWithEmptySlots(sharedComponentValues);
        if (chunk == ChunkIndex.Null)
            chunk = GetCleanChunk(archetype, sharedComponentValues);
        return chunk;
    }
}

注意到这里函数返回的是ChunkIndex类型,它不是真正意义上的chunk,而是对chunk的二次封装,用来方便快捷地存取chunk的数据,以及记录chunk的各种状态与属性。ChunkIndex只有一个int类型的value变量,表示当前chunk在所有chunk中的位置。虽然Unity声称每个chunk只有16KB大小,但是实际分配内存时,并不是按16KB进行分配,而是会一口气先分配好1 << 20字节也就是64 * 16KB大小的内存,这块内存被称为mega chunk。所有mega chunk的内存指针都保存在大小为16384个ulong类型的连续内存中,因此ChunkIndex记录的是全局索引,第一步先索引到某个mega chunk,然后再定位到真正的chunk地址。

在这里插入图片描述

internal Chunk* GetChunkPointer(int chunkIndex)
{
    var megachunkIndex = chunkIndex >> log2ChunksPerMegachunk;
    var chunkInMegachunk = chunkIndex & (chunksPerMegachunk-1);
    var megachunk = (byte*)m_megachunk.ElementAt(megachunkIndex);
    var chunk = megachunk + (chunkInMegachunk << Log2ChunkSizeInBytesRoundedUpToPow2);
    return (Chunk*)chunk;
}

Chunk是一个相对简单的数据结构,前64字节为chunk头部,后面才是数据内容。

[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Chunk
{
    // Chunk header START

    // The following field is only used during serialization and won't contain valid data at runtime.
    // This is part of a larger refactor, and this field will eventually be removed from this struct.
    [FieldOffset(0)]
    public int ArchetypeIndexForSerialization;
    // 4-byte padding to keep the file format compatible until further changes to the header.

    [FieldOffset(8)]
    public Entity metaChunkEntity;

    // The following field is only used during serialization and won't contain valid data at runtime.
    // This is part of a larger refactor, and this field will eventually be removed from this struct.
    [FieldOffset(16)]
    public int CountForSerialization;

    [FieldOffset(28)]
    public int ListWithEmptySlotsIndex;

    // Special chunk behaviors
    [FieldOffset(32)]
    public uint Flags;

    // SequenceNumber is a unique number for each chunk, across all worlds. (Chunk* is not guranteed unique, in particular because chunk allocations are pooled)
    [FieldOffset(kSerializedHeaderSize)]
    public ulong SequenceNumber;

    // NOTE: SequenceNumber is not part of the serialized header.
    //       It is cleared on write to disk, it is a global in memory sequence ID used for comparing chunks.
    public const int kSerializedHeaderSize = 40;

    // Chunk header END

    // Component data buffer
    // This is where the actual chunk data starts.
    // It's declared like this so we can skip the header part of the chunk and just get to the data.
    public const int kBufferOffset = 64; // (must be cache line aligned)
    [FieldOffset(kBufferOffset)]
    public fixed byte Buffer[4];

    public const int kChunkSize = 16 * 1024;
    public const int kBufferSize = kChunkSize - kBufferOffset;
    public const int kMaximumEntitiesPerChunk = kBufferSize / 8;

    public const int kChunkBufferSize = kChunkSize - kBufferOffset;
}

随后,Unity会在该chunk中为要创建的entity分配索引,索引为当前chunk的entity数量,因为之前提到chunk的数据都是连续排布的,中间并不存在空隙。

static int AllocateIntoChunk(Archetype* archetype, ChunkIndex chunk, int count, out int outIndex)
{
    outIndex = chunk.Count;
    var allocatedCount = Math.Min(archetype->ChunkCapacity - outIndex, count);
    SetChunkCount(archetype, chunk, outIndex + allocatedCount);
    archetype->EntityCount += allocatedCount;
    return allocatedCount;
}

根据索引,就可以将chunk中属于该entity的部分(entity id,component type)进行初始化,clear内存等操作,自此,一个entity就算创建完成了。

for (var t = 1; t != typesCount; t++)
{
    var offset = offsets[t];
    var sizeOf = sizeOfs[t];
    var dst = dstBuffer + (offset + sizeOf * dstIndex);

    if (types[t].IsBuffer)
    {
        for (var i = 0; i < count; ++i)
        {
            BufferHeader.Initialize((BufferHeader*)dst, bufferCapacities[t]);
            dst += sizeOf;
        }
    }
    else
    {
        UnsafeUtility.MemClear(dst, sizeOf * count);
    }
}

接下来,再为entity添加component,那么原来entity所在的chunk和archetype不再满足要求,需要将entity移动到新的chunk和archetype中,这种行为在Unity中被称作structural change。在AddComponent之前,Unity首先会检查entity是否已有该component,同一类型的component只允许存在一个。这个时候archetype就发挥作用了,只需要去archetype中查找component type是否存在即可:

public bool HasComponent(Entity entity, ComponentType type)
{
    if (Hint.Unlikely(!Exists(entity)))
        return false;

    var archetype = GetArchetype(entity);
    return ChunkDataUtility.GetIndexInTypeArray(archetype, type.TypeIndex) != -1;
}

如果entity没有该component,那么就需要找一下符合条件的archetype以及空闲的chunk。

ChunkIndex GetChunkWithEmptySlotsWithAddedComponent(ChunkIndex srcChunk, ComponentType componentType, int sharedComponentIndex = 0)
{
    var archetypeChunkFilter = GetArchetypeChunkFilterWithAddedComponent(srcChunk, componentType, sharedComponentIndex);
    if (archetypeChunkFilter.Archetype == null)
        return ChunkIndex.Null;

    return GetChunkWithEmptySlots(ref archetypeChunkFilter);
}

新的archetype和chunk都有了,剩下的就是move操作了,先把数据从源chunk拷贝到目标chunk中去,更新目标archetype,然后再清理源chunk,更新源archetype。

int Move(in EntityBatchInChunk srcBatch, ChunkIndex dstChunk)
{
    var srcChunk = srcBatch.Chunk;
    var srcArchetype = GetArchetype(srcChunk);
    var dstArchetype = GetArchetype(dstChunk);
    var dstUnusedCount = dstArchetype->ChunkCapacity - dstChunk.Count;
    var srcCount = math.min(dstUnusedCount, srcBatch.Count);
    var srcStartIndex = srcBatch.StartIndex + srcBatch.Count - srcCount;

    var partialSrcBatch = new EntityBatchInChunk
    {
        Chunk = srcChunk,
        StartIndex = srcStartIndex,
        Count = srcCount
    };

    ChunkDataUtility.Clone(srcArchetype, partialSrcBatch, dstArchetype, dstChunk);

    fixed (EntityComponentStore* store = &this)
    {
        ChunkDataUtility.Remove(store, partialSrcBatch);
    }

    return srcCount;
}

前面提到过,当从chunk中移除entity时,chunk的最后一个entity将被移动以填补空隙。其实就是个简单的拷贝内存操作:

var fillStartIndex = chunk.Count - fillCount;

Copy(entityComponentStore, chunk, fillStartIndex, chunk, startIndex, fillCount);

Reference

[1] Archetypes concepts

[2] 深入理解ECS:Archetype(原型)

[3] Archetypes window reference

[4] Structural changes concepts

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统:本机(物理主机)访问不了虚拟机中的apache服务问题的解决方案

学习目标&#xff1a; 提示&#xff1a;本文主要讲述-本机(物理主机)访问不了虚拟机中的apache服务情况下的解决方案 Linux系统&#xff1a;Ubuntu 23.04&#xff1b; 文中提到的“本机”&#xff1a;代表&#xff0c;宿主机&#xff0c;物理主机&#xff1b; 首先&#xff0c…

linux 中mysql my.cnf 配置模版

前置准备 sudo systemctl stop mysqld 注意&#xff1a; 原本配置重命名做备份 备份数据 删文件 直接新建 my.cnf 把配置 11要粘进去的内容 直接粘进去 注意&#xff1a;尽管log-bin 和 log_bin 都可以启用二进制日志&#xff0c;但为了保持与现代MySQL版本的兼容性和一…

物流行业创新:SpringBoot技术应用

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

24.安卓逆向-frida基础-objection工具3-实战

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

全方面熟悉Maven项目管理工具(五)教你IDEA创建Maven基本项目、导入工程和模块,并为你讲解Maven的生命周期

1. IDEA 配置 Maven 本身 IDEA 就可以在一个项目中创建多个子模块。 1.1 创建父工程 如果要构建不同的 Maven 工程类型&#xff0c;可以勾选【从原型创建】在列表中选择工程类型 1.2 IDEA 配置 Maven 本地仓库 在设置中前往&#xff1a;构建、执行、部署 >> 构建工具…

鸿蒙网络编程系列28-服务端证书锁定防范中间人攻击示例

1. TLS通讯中间人攻击及防范简介 TLS安全通讯的基础是基于对操作系统或者浏览器根证书的信任&#xff0c;如果CA证书签发机构被入侵&#xff0c;或者设备内置证书被篡改&#xff0c;都会导致TLS握手环节面临中间人攻击的风险。其实&#xff0c;这种风险被善意利用的情况还是很…

Linux基础项目开发day06:量产工具——业务系统

文章目录 前言一、流程代码框架1、业务系统框架流程2、主页面流程图3、main.c实现流程 二、处理配置文件1、配置文件是啥&#xff1f;config.h 2、怎么处理配置文件&#xff1f;config.c 三、生成界面1、计算每个按钮的Region2、逐个生成按钮画面->生成页面 四、读取输入事件…

记录一次hiveserver2卡死(假死)问题

问题描述 给开发人员开通了个账号&#xff0c;连接hive进行查询&#xff0c;后来发现&#xff0c;hive服务有时候会卡死&#xff0c;查询不了&#xff0c;连不上&#xff08;所有账号/客户端都连不上hive&#xff09;&#xff0c;但在chd里面看监控&#xff0c;服务器资源状态…

物联网之超声波测距模块、arduino、esp32

MENU 原理硬件电路设计软件程序设计 原理 超声波是一种频率高于20000Hz的声波&#xff0c;功率密度为p≥0.3W/cm&#xff0c;它的方向性好&#xff0c;反射能力强&#xff0c;易于获得较集中的声能。超声波用于许多不同的领域&#xff0c;比如检测物体和测量距离&#xff0c;清…

Unity 2d UI 实时跟随场景3d物体

2d UI 实时跟随场景3d物体位置&#xff0c;显示 3d 物体头顶信息&#xff0c;看起来像是场景中的3dUI&#xff0c;实质是2d UIusing System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using UnityEngine.UI; /// <summary>…

【JS】无法阻止屏幕滚动

监听滚轮事件&#xff0c;阻止默认行为&#xff0c;但未生效&#xff0c;且控制台报错。 window.addEventListener(wheel, (e) > {e.preventDefault(); })这是因为现代浏览器使用 Passive 事件监听器&#xff0c;默认启用了 passive 模式以确保性能&#xff0c;不会调用 pr…

Cancer Cell|最新发表的单细胞成纤维细胞分析代码,速来学习!!!

简介 成纤维细胞在维持组织稳态、应对炎症和纤维化状况、帮助伤口愈合以及促进癌症进展的复杂舞蹈中起着关键作用。在癌症领域&#xff0c;成纤维细胞已成为肿瘤微环境&#xff08;TME&#xff09;中的核心人物&#xff0c;发挥着多方面的作用。这些作用包括细胞外基质&#xf…

【深度学习实战—12】:基于MediaPipe的手势识别

✨博客主页&#xff1a;王乐予&#x1f388; ✨年轻人要&#xff1a;Living for the moment&#xff08;活在当下&#xff09;&#xff01;&#x1f4aa; &#x1f3c6;推荐专栏&#xff1a;【图像处理】【千锤百炼Python】【深度学习】【排序算法】 目录 &#x1f63a;一、Med…

Java设计模式梳理:行为型模式(策略,观察者等)

行为型模式 行为型模式关注的是各个类之间的相互作用&#xff0c;将职责划分清楚&#xff0c;使得我们的代码更加地清晰。 策略模式 策略模式太常用了&#xff0c;所以把它放到最前面进行介绍。它比较简单&#xff0c;我就不废话&#xff0c;直接用代码说事吧。 下面设计的…

电能表预付费系统-标准传输规范(STS)(16)

6.3.9 MPL: MaximumPowerLimit&#xff08;最大功率限制&#xff09; The maximum power limit field is a 1 6-bit field that indicates the maximum power that the load may draw, in watts. Calculation of this field is identical to that of the TransferAmount field…

【JavaEE】——自定义协议方案、UDP协议

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;自定义协议 1&#xff1a;自定义协议 &#xff08;1&#xff09;交互哪些信息 &…

数据库设计与开发—初识SQLite与DbGate

一、SQLite与DbGate简介 &#xff08;一&#xff09;SQLite[1][3] SQLite 是一个部署最广泛、用 C 语言编写的数据库引擎&#xff0c;属于嵌入式数据库&#xff0c;其作为库被软件开发人员嵌入到应用程序中。 SQLite 的设计允许在不安装数据库管理系统或不需要数据库管理员的情…

C++中的继承(1)

1.继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许我们在保持原有类特性的基础上进行扩展&#xff0c;增加⽅法(成员函数)和属性(成员变量)&#xff0c;这样产生新的类&#xff0c;称派生类&#xff08;也被称为子类&…

Vue 3集成海康Web插件实现视频监控

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;组件封装篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来组件封装篇专栏内容:Vue 3集成海康Web插件实现视频监控 引言 最近在项目中使用了 Vue 3 结合海康Web插件来实…

Django项目创建

安装 pip install django 创建项目 首先打开powershell打开项目与创建到的文件夹 django-admin startproject django_demo01 django-admin startproject 项目名 多出了一个django_demo01的文件夹&#xff0c;这就是我们的项目了 打开项目文件夹&#xff0c;发现一个文件和…