qframework 架构 (作者:凉鞋)使用笔记
一些准则:
根据VIEW->SYSTEM->MODEL的分层架构
初始架构:
app.
using FrameworkDesign;namespace ShootingEditor2D(项目的命名空间)
{public class ShootingEditor2D (游戏名称): Architecture<ShootingEditor2D>{protected override void Init(){}}
}
该脚本放到scripts文件夹下。
其他model\system等,注册在app.
using FrameworkDesign;namespace ShootingEditor2D
{public class ShootingEditor2D : Architecture<ShootingEditor2D>{protected override void Init(){this.RegisterModel<IPlayerModel>(new PlayerModel());this.RegisterSystem<IStatSystem>(new StatSystem());}}
}
功能列表

在VIEW层和SYSTEM层之间通信
创建VIEW
创建VIEW中的关键角色:UIController,方便快速对UI的显示进行处理。
*注意,VIEW脚本可以获取任何来自SYSTEM和MODEL的信息,以此来更新自己,比如监测是否子弹足够。
namespace ShootingEditor2D
{public class Gun : MonoBehaviour,IController // +{private Bullet mBullet;private GunInfo mGunInfo; // +private void Awake(){mBullet = transform.Find("Bullet").GetComponent<Bullet>();mGunInfo = this.GetSystem<IGunSystem>().CurrentGun; // +}public void Shoot(){if (mGunInfo.BulletCount.Value > 0) // +{var bullet = Instantiate(mBullet.transform, mBullet.transform.position, mBullet.transform.rotation);// 统一缩放值bullet.transform.localScale = mBullet.transform.lossyScale;bullet.gameObject.SetActive(true);this.SendCommand<ShootCommand>(); // +}}public IArchitecture GetArchitecture() // +{return ShootingEditor2D.Interface;}private void OnDestroy() // +{mGunInfo = null; //+}}
}
namespace ShootingEditor2D
{public class UIController : MonoBehaviour,IController{private IStatSystem mStatSystem;private IPlayerModel mPlayerModel;private void Awake(){mStatSystem = this.GetSystem<IStatSystem>();mPlayerModel = this.GetModel<IPlayerModel>();}/// <summary>/// 自定义字体大小/// </summary>private readonly Lazy<GUIStyle> mLabelStyle = new Lazy<GUIStyle>(()=>new GUIStyle(GUI.skin.label){fontSize = 40});private void OnGUI(){GUI.Label(new Rect(10,10,300,100),$"生命:{mPlayerModel.HP.Value}/3",mLabelStyle.Value);GUI.Label(new Rect(Screen.width - 10 - 300,10,300,100),$"击杀数量:{mStatSystem.KillCount.Value}",mLabelStyle.Value);}public IArchitecture GetArchitecture(){return ShootingEditor2D.Interface;}}
}
创建VIEW->SYSTEM的通信方式:命令Command
namespace ShootingEditor2D
{public class KillEnemyCommand : AbstractCommand{protected override void OnExecute(){this.GetSystem<IStatSystem>().KillCount.Value++;}}
}
protected override void OnExecute(){var gunSystem = this.GetSystem<IGunSystem>();gunSystem.CurrentGun.BulletCountInGun.Value--;gunSystem.CurrentGun.GunState.Value = GunState.Shooting;}
确定在什么运行情况下发出该命令Command。比如,一个小小的子弹销毁时,子弹知道要发出。
namespace ShootingEditor2D
{public class Bullet : MonoBehaviour,IController // +{private Rigidbody2D mRigidbody2D;private void Awake(){mRigidbody2D = GetComponent<Rigidbody2D>();}private void Start(){mRigidbody2D.velocity = Vector2.right * 10;}private void OnCollisionEnter2D(Collision2D other){if (other.gameObject.CompareTag("Enemy")){this.SendCommand<KillEnemyCommand>(); // +Destroy(other.gameObject);}}public IArchitecture GetArchitecture() // +{return ShootingEditor2D.Interface;}}
}
再比如,如果玩家碰到怪物就掉血,那么针对这个功能可以写一个脚本,挂在再玩家身上
namespace ShootingEditor2D
{public class AttackPlayer : MonoBehaviour,IController{private void OnCollisionEnter2D(Collision2D other){if (other.gameObject.CompareTag("Player")){this.SendCommand<HurtPlayerCommand>();}}public IArchitecture GetArchitecture(){return ShootingEditor2D.Interface;}}
VIEW自己分内的逻辑就自己处理了,比如怪物碰到玩家自己消失。如果不需要记录MODEL以数据的话,就自己删除。
可以建立各种各样的VIEW,别客气。
比如碰到了子弹库来补充弹药:
public class GunPickItem : MonoBehaviour,IController{public string Name;public int BulletCountInGun;public int BulletCountOutGun;private void OnTriggerEnter2D(Collider2D other){if (other.CompareTag("Player")){this.SendCommand(new PickGunCommand(Name,BulletCountInGun,BulletCountOutGun));}}public IArchitecture GetArchitecture(){return ShootingEditor2D.Interface;}}
条件没问题的话,发送就行。至于这个pickgun要如何运作,有点复杂,但由command交给system去处理,处理之后,发送回一个event,表示枪支变化:
command这样写:
public class PickGunCommand : AbstractCommand{private readonly string mName;private readonly int mBulletInGun;private readonly int mBulletOutGun;public PickGunCommand(string name, int bulletInGun, int bulletOutGun){mName = name;mBulletInGun = bulletInGun;mBulletOutGun = bulletOutGun;}protected override void OnExecute(){this.GetSystem<IGunSystem>().PickGun(mName, mBulletInGun, mBulletOutGun);}}
system这样写
using System.Collections.Generic;
using System.Linq;
using FrameworkDesign;namespace ShootingEditor2D
{public interface IGunSystem : ISystem{GunInfo CurrentGun { get; }void PickGun(string name, int bulletCountInGun, int bulletCountOutGun); //+}public class OnCurrentGunChanged // +{public string Name { get; set; }}public class GunSystem : AbstractSystem, IGunSystem{protected override void OnInit(){}private Queue<GunInfo> mGunInfos = new Queue<GunInfo>(); // +public GunInfo CurrentGun { get; } = new GunInfo(){BulletCountInGun = new BindableProperty<int>(){Value = 3},BulletCountOutGun = new BindableProperty<int>(){Value = 1},Name = new BindableProperty<string>(){Value = "手枪"},GunState = new BindableProperty<GunState>(){Value = GunState.Idle}};public void PickGun(string name, int bulletCountInGun, int bulletCountOutGun) // +{// 当前枪是同类型if (CurrentGun.Name.Value == name){CurrentGun.BulletCountOutGun.Value += bulletCountInGun;CurrentGun.BulletCountOutGun.Value += bulletCountOutGun;}// 已经拥有这把枪了else if (mGunInfos.Any(info => info.Name.Value == name)){var gunInfo = mGunInfos.First(info => info.Name.Value == name);gunInfo.BulletCountOutGun.Value += bulletCountInGun;gunInfo.BulletCountOutGun.Value += bulletCountOutGun;}else{// 复制当前的枪械信息var currentGunInfo = new GunInfo{Name = new BindableProperty<string>(){Value = CurrentGun.Name.Value},BulletCountInGun = new BindableProperty<int>(){Value = CurrentGun.BulletCountInGun.Value},BulletCountOutGun = new BindableProperty<int>(){Value = CurrentGun.BulletCountOutGun.Value},GunState = new BindableProperty<GunState>(){Value = CurrentGun.GunState.Value}};// 缓存mGunInfos.Enqueue(currentGunInfo);// 新枪设置为当前枪CurrentGun.Name.Value = name;CurrentGun.BulletCountInGun.Value = bulletCountInGun;CurrentGun.BulletCountOutGun.Value = bulletCountOutGun;CurrentGun.GunState.Value = GunState.Idle;// 发送换枪事件this.SendEvent(new OnCurrentGunChanged(){Name = name});}}}
}
SYSTEM中的数据变化如何告知VIEW?——为SYSTEM中的数据套用BindableProperty!
namespace ShootingEditor2D
{public enum GunState{Idle,Shooting,Reload,EmptyBullet,CoolDown}public class GunInfo{[Obsolete("请使用 BulletCountInGame",true)] // 第二个参数改成了 truepublic BindableProperty<int> BulletCount{get => BulletCountInGun;set => BulletCountInGun = value;}public BindableProperty<int> BulletCountInGun;public BindableProperty<string> Name;public BindableProperty<GunState> GunState;public BindableProperty<int> BulletCountOutGun;}
}
也可以设好初始值(但这个和架构无关)
public GunInfo CurrentGun { get; } = new GunInfo(){BulletCountInGun = new BindableProperty<int>(){Value = 3},BulletCountOutGun = new BindableProperty<int>() // +{Value = 1},Name = new BindableProperty<string>() // +{Value = "手枪"},GunState = new BindableProperty<GunState>() // +{Value = GunState.Idle}};
事件EVENT:作为SYSTEM通知VIEW的方式。VIEW要自己Register
比如:这样
public class UIController : MonoBehaviour, IController{private IStatSystem mStatSystem;private IPlayerModel mPlayerModel;private IGunSystem mGunSystem;private int mMaxBulletCount;private void Awake(){mStatSystem = this.GetSystem<IStatSystem>();mPlayerModel = this.GetModel<IPlayerModel>();mGunSystem = this.GetSystem<IGunSystem>();// 查询代码mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value));this.RegisterEvent<OnCurrentGunChanged>(e =>{mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(e.Name));}).UnRegisterWhenGameObjectDestroyed(gameObject); // +}
MODEL:作为数据记录层

using System.Collections.Generic;
using FrameworkDesign;namespace ShootingEditor2D
{public interface IGunConfigModel : IModel{GunConfigItem GetItemByName(string name);}public class GunConfigItem{public GunConfigItem(string name, int bulletMaxCount, float attack, float frequency, float shootDistance,bool needBullet, float reloadSeconds, string description){Name = name;BulletMaxCount = bulletMaxCount;Attack = attack;Frequency = frequency;ShootDistance = shootDistance;NeedBullet = needBullet;ReloadSeconds = reloadSeconds;Description = description;}public string Name { get; set; }public int BulletMaxCount { get; set; }public float Attack { get; set; }public float Frequency { get; set; }public float ShootDistance { get; set; }public bool NeedBullet { get; set; }public float ReloadSeconds { get; set; }public string Description { get; set; }}public class GunConfigModel : AbstractModel, IGunConfigModel{private Dictionary<string, GunConfigItem> mItems = new Dictionary<string, GunConfigItem>(){{ "手枪", new GunConfigItem("手枪", 7, 1, 1, 0.5f, false, 3, "默认强") },{ "冲锋枪", new GunConfigItem("冲锋枪", 30, 1, 6, 0.34f, true, 3, "无") },{ "步枪", new GunConfigItem("步枪", 50, 3, 3, 1f, true, 1, "有一定后坐力") },{ "狙击枪", new GunConfigItem("狙击枪", 12, 6, 1, 1f, true, 5, "红外瞄准+后坐力大") },{ "火箭筒", new GunConfigItem("火箭筒", 1, 5, 1, 1f, true, 4, "跟踪+爆炸") },{ "霰弹枪", new GunConfigItem("霰弹枪", 1, 1, 1, 0.5f, true, 1, "一次发射 6 ~ 12 个子弹") },};protected override void OnInit(){}public GunConfigItem GetItemByName(string name){return mItems[name];}}
}
其他好东西:
1.时间冷却系统
timeSystem.AddDelayTask(0.33f, () =>{gunSystem.CurrentGun.GunState.Value = GunState.Idle;});
using System;
using System.Collections.Generic;
using FrameworkDesign;
using UnityEngine;namespace ShootingEditor2D
{public interface ITimeSystem : ISystem{float CurrentSeconds { get; }void AddDelayTask(float seconds, Action onFinish);}public enum DelayTaskState{NotStart,Started,Finish}public class DelayTask{public float Seconds { get; set; }public Action OnFinish { get; set; }public float StartSeconds { get; set; }public float FinishSeconds { get; set; }public DelayTaskState State { get; set; }}public class TimeSystem : AbstractSystem,ITimeSystem{public class TimeSystemUpdateBehaviour : MonoBehaviour{public event Action OnUpdate;private void Update(){OnUpdate?.Invoke();}}protected override void OnInit(){var updateBehaviourGameObj = new GameObject(nameof(TimeSystemUpdateBehaviour));UnityEngine.Object.DontDestroyOnLoad(updateBehaviourGameObj);// 如果需要销毁,可以缓存为成员变量var updateBehaviour = updateBehaviourGameObj.AddComponent<TimeSystemUpdateBehaviour>();updateBehaviour.OnUpdate += OnUpdate;}public float CurrentSeconds { get;private set; } = 0.0f;private LinkedList<DelayTask> mDelayTasks = new LinkedList<DelayTask>();private void OnUpdate(){CurrentSeconds += Time.deltaTime;if (mDelayTasks.Count > 0){var currentNode = mDelayTasks.First;while (currentNode != null){var delayTask = currentNode.Value;var nextNode = currentNode.Next;if (delayTask.State == DelayTaskState.NotStart){delayTask.State = DelayTaskState.Started;delayTask.StartSeconds = CurrentSeconds;delayTask.FinishSeconds = CurrentSeconds + delayTask.Seconds;} else if (delayTask.State == DelayTaskState.Started){if (CurrentSeconds > delayTask.FinishSeconds){delayTask.State = DelayTaskState.Finish;delayTask.OnFinish.Invoke();delayTask.OnFinish = null;mDelayTasks.Remove(currentNode); // 删除节点}}currentNode = nextNode;}}}public void AddDelayTask(float seconds, Action onFinish){var delayTask = new DelayTask(){Seconds = seconds,OnFinish = onFinish,State = DelayTaskState.NotStart,};mDelayTasks.AddLast(new LinkedListNode<DelayTask>(delayTask));}}
}
2、Query查询类
// 查询代码
var gunConfigModel = this.GetModel<IGunConfigModel>();
var gunConfigItem = gunConfigModel.GetItemByName(mGunSystem.CurrentGun.Name.Value);
mMaxBulletCount = gunConfigItem.BulletMaxCount;
上面的查询显得很臃肿
可以这样:
mGunSystem = this.GetSystem<IGunSystem>();// 查询代码
mMaxBulletCount = new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value).Do(); // -+
做法就是:写一个查询类
using FrameworkDesign;namespace ShootingEditor2D
{public class MaxBulletCountQuery : IBelongToArchitecture,ICanGetModel{private readonly string mGunName;public MaxBulletCountQuery(string gunName){mGunName = gunName;}public int Do(){var gunConfigModel = this.GetModel<IGunConfigModel>();var gunConfigItem = gunConfigModel.GetItemByName(mGunName);return gunConfigItem.BulletMaxCount;}public IArchitecture GetArchitecture(){return ShootingEditor2D.Interface;}}
}
通过一些修改可以直接通过架构Arch来发送查询,做到这样(代码略)
mGunSystem = this.GetSystem<IGunSystem>();// 查询代码
mMaxBulletCount = this.SendQuery(newMaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value)); // -+
3.凉鞋的话
(GamePix 独立游戏学院 - 让独立游戏不再难做 - Powered By EduSoho)欢迎来这里购买决定版的QFRAMEWORK课程。
此文为 决定版群里的聊天记录。
说一下,第一季的整体流程。
课程的最开始,是没有用任何架构就做一个《点点点》这样的项目,做的方式就是用拖拽加一点代码的方式。
但是这种方式有一个问题,就是对象和对象之间的相互访问特别乱,没有规则,也没有限制,这样下去当项目有一定规模了就会变得非常乱。于是就引入了一个规则,就是只有自顶向下的时候可以直接访问对象或者调用对象的方法。然后自底向上的时候使用事件或者委托。而在讲这个规则之前还介绍了对象之间的三种交互方式:方法调用、委托、事件。
然后自底向上和自顶向再加上对象之间的三种交互方式这个构成了一个大的前提,后边的比如层级、业务模块等只要有高低之分的我们就都用这套规则去约束了。
再往下就介绍了一个简单的模块化,介绍了一个单例的模块化。我们在做设计的时候经常听到一个原则,就是高内聚松耦合,意高内聚意思是相同的代码放在一个地方去管理,这个是高内聚,低耦合就是对象之间的引用不要太多,最好就是单向引用或者没有引用,或者是有一定的规则去约束如何互相访问。
再往下就引入了一个概念,就是 Model,Model 是因为什么引入的呢?是因为就是有一些数据,它需要在多个地方去共享,比如角色的攻击力,需要在 UI 界面上显示,或者计算一个伤害的时候需要使用,总之需要在多个地方去使用它,而这种数据就是需要共享的数据,甚至需要把攻击力存储起来,而存储也是一种 共享方式,比如上次游戏关闭到了,现在打开游戏之后角色的攻击力不能变成初始攻击力了,所以数据的存储也是一种共享方式。而这些需要存储的数据,就需要放到 Model 里管理起来。而 Model 在决定版架构的引入就是因为有了需要共享的数据才引入的。
引入完 Model 之后就要考虑一个问题,就是其他的地方怎么跟这个 Model 进行交互,然后交互的部分,一般的方式就是用类似 MVC 的方式,然后其中 MVC 中的 Controller ,它所管理的逻辑分成了交互逻辑和表现逻辑。
大家都说 MVC 中的 Controller 代码容易臃肿起来,那么罪魁祸首就是交互逻辑,只要是有数据操作或者变更游戏数据状态的逻辑都是交互逻辑,而这部分逻辑是非常多的,要想解决 Controller 代码臃肿的问题,一般的共识就是引入命令模式,也就是 Command,让 Command 去分担 Controller 的交互逻辑,Controller 仅仅只是调用相应的 Command 即可。
好多的框架或者方案都是用 Command 去分担交互逻辑的,所以这里就不赘述笔者为啥用 Command 了。
引入了 Command 之后,Controller 就变成了比较薄的一层了,而 Command 并不适合负责所有的交互逻辑,比如有的交互逻辑最好还是统一放在一个对象里,比如分数统计、成就检测、任务检测等,如果分数统计这种逻辑分散在各种 Command 里,会非常不好管理,而分数统计实际上是一种规则类的逻辑,比如打一个敌人得多少分等等,最好是统一放在一个对象里管理,于是就引入了 System 层,System 层就是管理需要统一管理的交互逻辑而引入的,比如成就系统、任务系统等,这些系统一般会写很多死代码,那么这些死代码分散到各个 Command 里,想想都觉得恐怖,所以最好要弄脏就弄脏一个对象就是系统对象,这也是高内聚的一种体现。
到这里架构的一些核心概念就有了雏形了,像事件机制、BindableProperty 等都是一些通用的工具。
再接着架构雏形有了之后,就开始不断打磨这套架构的实现,这部分的内容就是一些 C# 的高级使用方法,用各种技巧达成设计目的,就不多说了。
总之架构中的每一个概念的引入都是为了解决特定的架构问题的,并不是为了做成架构而引入的,然后只要不断地去解决这些架构问题,就会慢慢迭代出来一个比较成型的框架/架构。
最后笔者简单再说一点,就是第一季的内容就是迭代这套架构,在迭代过程中不仅仅只有代码实现的部分,更重要的还是引入这些概念解决哪些问题,所以在学习课程的时候要重点放在概念解决哪些问题上,只要这块了解了,就会对各个概念的使用不会出现问题。
到了第二季 对一些架构本身的问题做了一些改进,比如 BindableProperty 去掉 IEquatable 接口,因为没必要,然后实现了完整的 CQRS 机制,也就是引入了 Query,有了 Query 之后实现充血模型不再是难事。
相关文章:
qframework 架构 (作者:凉鞋)使用笔记
一些准则: 根据VIEW->SYSTEM->MODEL的分层架构 初始架构: app. using FrameworkDesign;namespace ShootingEditor2D(项目的命名空间) {public class ShootingEditor2D (游戏名称): Architecture&l…...
【JMeter】定时器分类以及场景介绍
1. 定时器分类 固定定时器 作用:请求之间设置等待时间应用场景:查询商品列表后,去查看列表商品详情页。针对商品列表数据量比较大的,响应时间会比较长,就需要设置等待时间然后去查看商详 2.定时器的作用域࿱…...
Spring Boot 请求/actuator/beans 无法访问 返回404
问题复现 在保证项目加入了spring-boot-starter-actuator依赖,并成功启动后。通过浏览器进行访问,返回如下图结果: 问题排查 1. 查看日志 从日志中可以看到基于路径’/actuator’下只暴露了一个端点 2. 访问http://localhost:8080/actua…...
AVD联网
AVD联网: 解决Android Studio模拟器无法联网_android studio模拟器没有网络-CSDN博客 挺好的,就是访问网站的时候只能用ip,而不能用域名。 AVD设置代理: android studio踩坑记 AVD模拟器代理设置_android studio avd 配置代理-…...
[Vue warn]: Missing required prop: “action“
控制台显示错误信息 vue.runtime.esm.js:4605 [Vue warn]: Missing required prop: "action" found in ---> <ElUpload> at packages/upload/src/index.vue <ElTableRow> <ElTableBody> <ElTable> at pack…...
Python标准库有哪些
概述 可用性注释 内置函数 内置常量 由 site 模块添加的常量 # Author : 小红牛 # 微信公众号:wdPython内置类型 逻辑值检测 布尔运算 — and, or, not 比较运算 数字类型 — int, float, complex 布尔类型 - bool 迭代器类型 序列类型 — list, tuple, range 文本…...
基于ssm的校园办公室报修管理系统
基于ssm的校园办公室报修管理系统 摘要 基于SSM的校园办公室报修管理系统是一个现代化的、高效的报修平台,它能够帮助校园内的教职工和学生更方便、更快捷地提交和处理报修请求。该系统基于Spring、SpringMVC和MyBatis(简称SSM)开发ÿ…...
1Panel 升级 Halo报错
1Panel 升级 Halo报错 通过 1panel 升级 2.10.0 -> 2.10.1 后启动失败,出现 No value found for protocol 错误, 1Panel-halo-rzxY | Caused by: io.r2dbc.spi.NoSuchOptionException: No value found for protocol 1Panel-halo-rzxY | at io.r2dbc.spi.Conn…...
spring-clound基础开发
一、使用openfeig调用远程另外一个服务接口 1、创建一个spring boot工程,并且创建2个模块来当微服务模块 2、分别配置2个模块的启动文件 3、分别两个模块下创建一个测试的控制器 4、在项目的根目录的pom.xml中添加spring-cloud配置 <properties><java.version>1…...
基于SSM的劳务外包管理系统的设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...
uni-app学习笔记(二)
目录 一、路由与页面跳转 1、tabar与普通页面跳转例子 2、navigateTo 3、switchTab 二、vue组件 1、传统vue组件的使用 2、easycom 三、uView组件库 1、安装配置 2、引入配置 3、使用 四、Vuex 1、认识 2、state基本使用 3、mapState使用 五、网络请求 1、封装…...
使用axios拦截器解决前端并发冲突问题
使用 axios 拦截器解决「 前端并发冲突 」 问题 背景 并发冲突问题, 是日常开发中一个比较常见的问题。 不同用户在较短时间间隔内变更数据,或者某一个用户进行的重复提交操作都可能导致并发冲突。 并发场景在开发和测试阶段难以排查全面,…...
IPv6详解
目录: 第一部分 IPv6的诞生背景和引起的主要变化 第二部分 IPv6数据报的基本首部和扩展首部 第三部分 IPv6地址 第四部分 IPv4向IPv6过渡 第一部分 IPv6的诞生背景和引起的主要变化 一.IPv6的诞生背景 IPv4存在设计缺陷: IPv4的设计者最初并没有想到该协议会在全球范围内广…...
【C++干货铺】STL简述 | string类的使用指南
个人主页点击直达:小白不是程序媛 C系列专栏:C干货铺 代码仓库:Gitee 目录 什么是STL STL的版本 STL的六大组件 STL的缺陷 string类 C语言中的字符串 标准库中的string类 string类常用的接口使用指南 string类中常见的构造 strin…...
合肥工业大学数字逻辑实验三
** 数字逻辑 实验报告** ✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :hfut实验课设 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台,永远是个观众。平台再好,你不参与,永远是局外人。能力再大,你不行动,只能看别人成功!…...
【mmrotate】*** is not in the task util registry
问题: 使用mmrotate-1.x 自定义类时,明明已经注册,并添加到__init__.py中,但提示没有注册 from mmdet.registry import MODELSMODELS.register_module() class RotatedATSSAssigner(BaseAssigner): 分析: 具体看提…...
使用bitmap实现可回收自增id
需求描述 设计一个方法,每次调用返回一个自增id,同时需要满足以下要求。 可更新id的状态为已使用,已使用的id下次调用时不再返回可修改某个id的状态为未使用,下次调用时设为未使用状态的id可重新被返回 思路 思路一࿱…...
0基础学习VR全景平台篇第118篇:利用动作录制器功能避免重复操作 - PS教程
上课!全体起立~ 大家好,欢迎观看蛙色官方系列全景摄影课程! 嗨,大家好。欢迎收看蛙色VR系列教程之PS利用动作记录器节约补地时间。 大家拍摄在补地的时候,利用插件选择输入输出选项的时候,每次重复操作…...
大数据Doris(十九):数据导入(Load)
文章目录 数据导入(Load) 一、Broker load 二、Stream load 三、Insert 四、Multi load...
BP神经网络的数据分类——语音特征信号分类
大家好,我是带我去滑雪! BP神经网络,也称为反向传播神经网络,是一种常用于分类和回归任务的人工神经网络(ANN)类型。它是一种前馈神经网络,通常包括输入层、一个或多个隐藏层和输出层。BP神经网…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
