UE 事件分发机制(二) day10

自定义事件分发机制

自建事件分发机制与结构

  • Unreal推荐的游戏逻辑开发流程
    在这里插入图片描述
  • 基于 Unreal推荐的游戏逻辑开发流程,一般我们的整体规划也就是这样
    在这里插入图片描述
  • 大致结构类图
    在这里插入图片描述

创建接口类与管理类以及所需函数

  • 新建一个Unreal接口类作为接口
    在这里插入图片描述
  • 然后创建一个蓝图函数库的基类
    在这里插入图片描述

EventInterface接口类

EventInterface.h

  • 复习一下BlueprintNativeEvent这个参数:会在C++中提供一个默认的实现,然后蓝图中去覆盖它改写它,在蓝图中实现这个函数时,如果调用一个父类的版本,它会先调用C++里面加了_Implementation这个函数,然后再去做蓝图其他的操作
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "EventInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UEventInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class DISTRIBUTE_API IEventInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	UFUNCTION(BlueprintNativeEvent,Category="Event Dispather Tool")
	void OnReceiveEvent(UObject* Data);
};

EventInterface.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "EventInterface.h"

// Add default functionality here for any IEventInterface functions that are not pure virtual.

EventManager蓝图函数库的基类管理类

EventManager.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "EventManager.generated.h"

/**
 * 
 */
UCLASS()
class DISTRIBUTE_API UEventManager : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
private:
	//监听者
	static TMap<FString, TArray<UObject*>> AllListeners;

public:

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static void AddEventListener(FString EventName, UObject* Listener);

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static void RemoveEventListener(FString EventName, UObject* Listener);

	UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")
	static FString  DispatchEvent(FString EventName, UObject* Data);

	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Event Dispather Tool")
	static UObject* NameAsset(UClass* ClassType);
};

EventManager.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "EventManager.h"

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
}

void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
}

FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	return FString();
}

UObject* UEventManager::NameAsset(UClass* ClassType)
{
	return nullptr;
}

编写AddEventListener函数

  • 加入接口的头文件,判断添加进来的事件名字是否为空,监听指针是否为空,检测对象是否有效,是否实现了指定的接口类

  • Listener->IsValidLowLevel():检查对象是否有效

  • Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()):检测接口是否实现指定接口,这里传入的UEventInterface就是接口类中的参与蓝图的类名
    在这里插入图片描述

    //初始化监听器
    TMap<FString, TArray<UObject*>> UEventManager::AllListeners;
    
    void UEventManager::AddEventListener(FString EventName, UObject* Listener)
    {
    	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
    		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
    	{
    		return;
    	}
    }
    
  • 然后查找是否存在这个事件数组,如果为空说明目前没有这个事件,就重新添加一下,存在就直接添加事件进入到数组即可

  • AddEventListener函数完整代码逻辑

#include "EventManager.h"
#include "EventInterface.h"

//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
	{
		return;
	}

	//查找是否存在这个事件数组
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);

	//如果为空说明目前没有这个事件,就重新添加一下
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		TArray<UObject*> NewEventArr = { Listener };
		UEventManager::AllListeners.Add(EventName, NewEventArr);
	}
	//存在就直接添加事件进入到数组即可
	else
	{
		EventArr->Add(Listener);
	}
}

编写其他函数

RemoveEventListener函数编写

  • 查询事件数组中是否有这个事件,如果有就删除这个事件
void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	if (EventArr != nullptr && EventArr->Num() != 0)
	{
		EventArr->Remove(Listener);
	}
}

DispatchEvent函数编写

  • 分派事件,也是首先考虑事件是否存在,然后循环事件数组里事件,不存在的事件就直接剔除,存在的就进行派发事件通知,注意派发事件通知时也要进行安全检测
FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	//如果不存在此事件数组
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		return "'" + EventName + "'No Listener.";
	}
	//使用UE_LOG换行,方便查看
	FString ErrorInfo = "\n";

	for (int i = 0; i < EventArr->Num(); i++)
	{
		UObject* Obj = (*EventArr)[i];
		//安全判断一下事件是否存在
		if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
		{
			//不存在直接剔除
			EventArr->RemoveAt(i);
			//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bug
			i--;
		}
		//通过了安全检测就直接派遣事件通知
		else
		{
			UFunction* FUN = Obj->FindFunction("OnReceiveEvent");
			//安全检测这个FUN是否有效
			if (FUN == nullptr || !FUN->IsValidLowLevel())
			{
				//打印错误信息
				ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";
			}
			else
			{
				//调用
				Obj->ProcessEvent(FUN, &Data);
			}
		}
	}
	return ErrorInfo;
}

NameAsset函数编写

  • 这个函数是方便我们在蓝图中去创建某一个新的指定类实例
  • 加入头文件 #include “Engine.h”,使用NewObject函数来创建一个新的指定对象
  • UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
    • GetTransientPackage():返回一个临时包(主要用于对象池)的指针
    • ClassType:ClassType 是一个 UClass 指针,指定了新对象的类型。
UObject* UEventManager::NameAsset(UClass* ClassType)
{
	UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
	return Obj;
}

EventManager.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "EventManager.h"
#include "EventInterface.h"
#include "Engine.h"
//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;

void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
	if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||
		!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
	{
		return;
	}

	//查找是否存在这个事件数组
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	
	//如果为空说明目前没有这个事件,就重新添加一下
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		TArray<UObject*> NewEventArr = { Listener };
		UEventManager::AllListeners.Add(EventName, NewEventArr);
	}
	//存在就直接添加事件进入到数组即可
	else
	{
		EventArr->Add(Listener);
	}
}

void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	if (EventArr != nullptr && EventArr->Num() != 0)
	{
		EventArr->Remove(Listener);
	}
}

FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{
	TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);
	//如果不存在此事件数组
	if (EventArr == nullptr || EventArr->Num() == 0)
	{
		return "'" + EventName + "'No Listener.";
	}
	//使用UE_LOG换行,方便查看
	FString ErrorInfo = "\n";

	for (int i = 0; i < EventArr->Num(); i++)
	{
		UObject* Obj = (*EventArr)[i];
		//安全判断一下事件是否存在
		if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass()))
		{
			//不存在直接剔除
			EventArr->RemoveAt(i);
			//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bug
			i--;
		}
		//通过了安全检测就直接派遣事件通知
		else
		{
			UFunction* FUN = Obj->FindFunction("OnReceiveEvent");
			//安全检测这个FUN是否有效
			if (FUN == nullptr || !FUN->IsValidLowLevel())
			{
				//打印错误信息
				ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";
			}
			else
			{
				//调用
				Obj->ProcessEvent(FUN, &Data);
			}
		}
	}
	return ErrorInfo;
}

UObject* UEventManager::NameAsset(UClass* ClassType)
{
	UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
	return Obj;
}

自定义事件分发广播事件

  • 因为我们的自定义事件系统里面发布数据只有一个参数,所以我们新建一个Object类专门来放数据,需要传递数据时就通过这个Object类
    在这里插入图片描述
    在这里插入图片描述
  • 然后创建一个Pawn类作为SenderEvent发起者,开始广播事件
// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{
	Super::BeginPlay();
	
	FTimerHandle TimerHandle;
	auto Lambda = []()
	{
		//开始广播事件
		UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));
		Data->Param = FMath::RandRange(0, 100);
		//委派事件
		UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);
	};
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);
	
}

MyBPAndCpp_Sender.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyBPAndCpp_Sender.generated.h"

UCLASS()
class DISTRIBUTE_API AMyBPAndCpp_Sender : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AMyBPAndCpp_Sender();

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Properties")
	class UStaticMeshComponent* StaticMesh;
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

MyBPAndCpp_Sender.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyBPAndCpp_Sender.h"
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "EventDistributeTool/EventManager.h"
#include "Public/TimerManager.h"
#include "MyData.h"

// Sets default values
AMyBPAndCpp_Sender::AMyBPAndCpp_Sender()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	RootComponent = StaticMesh;

	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshAsset(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialAsset(TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"));
	if (StaticMeshAsset.Succeeded() && MaterialAsset.Succeeded())
	{
		StaticMesh->SetStaticMesh(StaticMeshAsset.Object);
		StaticMesh->SetMaterial(0, MaterialAsset.Object);
	}
}

// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{
	Super::BeginPlay();
	
	FTimerHandle TimerHandle;
	auto Lambda = []()
	{
		//开始广播事件
		UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));
		Data->Param = FMath::RandRange(0, 100);
		//委派事件
		UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);
	};
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);
	
}

// Called every frame
void AMyBPAndCpp_Sender::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void AMyBPAndCpp_Sender::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

自定义事件分发订阅事件

  • 新建一个Actor基类,然后派生三个子类来订阅事件测试,这三个子类需要继承EventInterface这个接口类,父类接口中方法有BlueprintNativeEvent反射参数,所以在这实现加后缀_Implementation
    在这里插入图片描述

Actor_Receive_R.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "EventDistributeTool/EventInterface.h"
#include "BPAndCpp/Actor_Receive.h"
#include "Actor_Receive_R.generated.h"

/**
 * 
 */
UCLASS()
class DISTRIBUTE_API AActor_Receive_R : public AActor_Receive, public IEventInterface
{
	GENERATED_BODY()
	
protected:
	virtual void BeginPlay() override;
public:
	virtual void OnReceiveEvent_Implementation(UObject* Data) override;
};

Actor_Receive_R.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "Actor_Receive_R.h"
#include "Engine/Engine.h"
#include "MyData.h"
#include "EventDistributeTool/EventManager.h"
void AActor_Receive_R::BeginPlay()
{
	Super::BeginPlay();
	//订阅事件
	UEventManager::AddEventListener("MyBPAndCpp_DispatchEvent", this);
}
void AActor_Receive_R::OnReceiveEvent_Implementation(UObject* Data)
{
	//打印到屏幕
	GEngine->AddOnScreenDebugMessage(INDEX_NONE, 10.f, FColor::Red, FString::Printf(TEXT("%i"), Cast<UMyData>(Data)->Param));
}
  • 运行结果
    在这里插入图片描述
  • 触发错误打印log的原因是因为,EventManager类中的都是静态方法,静态方法只有到下次编译之后才会被释放,所以在调试中就会这样触发log
  • 使用在视口中播放就不会触发log,在正式游戏中也不会触发,或者有正常的解绑事件也不会有问题
    在这里插入图片描述

自定义事件分发蓝图订阅与解绑事件

蓝图订阅事件

  • 蓝图订阅事件,创建一个Actor蓝图然后派生三个子蓝图,注意调用接口类中的事件时要添加接口后编译才能找到OnReceiveEvent,然后委派事件时的名字记得填写
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

解绑事件

  • 解绑调用RemoveEventListener函数即可
    在这里插入图片描述
  • C++中也是差不多调用RemoveEventListener函数即可
    在这里插入图片描述
  • 运行结果,只会播放一轮了
    在这里插入图片描述

当播放结束时自动移除事件

  • 这样也能解决C++中的触发错误打印log
  • 蓝图中结束后自动移除事件
    在这里插入图片描述
  • C++中是重写BeginDestroy函数,调用移除函数即可
    在这里插入图片描述
    在这里插入图片描述
  • 运行结果,这样就不会有log错误了
    在这里插入图片描述

自定义事件之蓝图广播事件

  • 将MyBPAndCpp_Sender类转换为蓝图,首先调用一下父类的BeginPlay这样C++那边的事件才会生效
    在这里插入图片描述
  • 编写蓝图逻辑,发布广播
    在这里插入图片描述
  • 然后找个Actor蓝图接收事件,测试,订阅一下这个事件名即可
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

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

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

相关文章

Python基础:推导式(Comprehensions)详解

1. 推导式概念 Python推导式&#xff08;comprehensions&#xff09;是一种简洁而强大的语法&#xff0c;用于从已存在的数据&#xff08;列表、元组、集合、字典等&#xff09;中创建新的数据结构。推导式包括&#xff1a; 列表推导式元组推导式字典推导式集合推导式 2. 列表…

mybatis参数输入 #{}和${}

1、建库建表 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO t_emp(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO…

基于ssm亚盛汽车配件销售业绩管理系统

摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让亚盛汽车配件销售信息的管理模式进行升级&#xff0c;也为了更好的维护亚盛汽车配件销售信息&#xff0c;亚盛汽车配件销售业绩管理系统的开…

快速操控鼠标行为!Vue鼠标按键修饰符让你事半功倍

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! ⭐ 专栏简介 欢迎来到前端入门之旅&#xff01;这个…

网络入门---网络编程预备知识

目录标题 ifconfigip地址和mac地址的区别端口号pid和端口号UDP和TCP的初步了解网络字节序socket套接字 ifconfig 通过指令ifconfig便可以查看到两个网络接口&#xff1a; 我们当前使用的是一个linux服务器并是一个终端设备&#xff0c;所以他只需要一个接口用来入网即可&…

甘草书店记:2023年10月15日 星期日 「等待也是人生的大事」

我常说&#xff0c;最好的人生是刚刚好。 财富不可少&#xff0c;也不必多&#xff0c;够用就好。爱情不要晚&#xff0c;也不要早&#xff0c;恰好就好。 可是人生活在社会中、自然中&#xff0c;不会万事由己。所以&#xff0c;等待是人生的必修课。 书店的装修设计和LOGO…

Tomcat及JDK下载安装(Linux系统)

前言 Tomcat是一个开源的Web应用服务器&#xff0c;由Apache软件基金会管理和维护。它的主要功能是处理来自客户端的HTTP请求&#xff0c;生成并返回响应结果。Tomcat不仅可以实现Java Servlet和JavaServer Pages&#xff08;JSP&#xff09;等Web编程模型的支持&#xff0c;也…

STM32开发学习(地址映射)

LED灯代码&#xff1a; #define PERIPH_BASE ((unsigned int)0x40000000)#define AHB1PERIPH_BASE (PERIPH_BASE 0x00020000)#define GPIOF_BASE (AHB1PERIPH_BASE 0x1400)#define GPIOF_MODER *(unsigned int*)(GPIOF_BASE0x00) #define GPIOF_BSRR *(uns…

使用自动化测试获取手机短信验证码

目前在职测试开发,,写一些脚本,个人认为这职业不科学不应该有的职业,测试就是测试,开发就是开发,运维还是老鸟,这行业总能折腾些莫名其妙的东西出来,刚做这行时学的第一门语言是bash shell, 去新去单位上班直接写了个一键搭建测试环境的测试脚本,本来不想干测试了,好好做微信小…

idea不需安装插件,自动生成mybatis-plus对应的实体类entity,带注解@TableName、@TableId、@TableField

目录 1、修改Generate poJOs.groovy文件 2、idea中连接数据库 3、生成entity代码 4、查看生成的实体类 1、修改Generate poJOs.groovy文件 在项目下方点击Scratches and Consoles→ Extensions→ Database Tools and SQL箭头→schema→ Generate POJOs.groovy 替换为以下文…

tomcat调优配置

一. 设置账户进入管理页面 通过浏览器进入Tomcat7的管理模块页面&#xff1a;http://localhost:8080/manager/status 按照提示&#xff0c;在Tomcat7服务器指定的位置修改配置文件&#xff08;conf/tomcat-users.xml&#xff09;&#xff0c;增加相应的用户和角色配置标签 <…

10个火爆的设计素材网站推荐

所谓聪明的女人没有米饭很难做饭&#xff0c;设计师也是如此。如何找到优秀的设计材料是每个设计师的痛点&#xff0c;国内材料网站收费&#xff0c;但也限制使用范围和期限&#xff0c;大多数外国设计网站不能打开或需要特殊互联网使用&#xff0c;有一定的安全风险。 作为一…

微服务实战系列之Redis(cache)

前言 云淡天高&#xff0c;落木萧萧&#xff0c;一阵西北风掠过&#xff0c;似寒刀。冬天渐渐变得更名副其实了。“暖冬”的说法有点言过其实了。——碎碎念 微服务实战系列之Cache微服务实战系列之Nginx&#xff08;技巧篇&#xff09;微服务实战系列之Nginx微服务实战系列之F…

【hacker送书第4期】推荐4本Java必读书籍(各送一本)

第4期图书推荐 Java从入门到精通&#xff08;第7版&#xff09;内容简介参与方式 项目驱动零基础学Java内容简介参与方式 深入理解Java高并发编程内容简介参与方式 Java编程讲义内容简介参与方式 Java从入门到精通&#xff08;第7版&#xff09; 内容简介 《Java从入门到精通&…

Linux下文件操作函数

一.常见IO函数 fopen fclose fread fwrite fseek fflush fopen 运行过程 &#xff1a;打开文件 写入数据 数据写到缓冲区 关闭文件后 将数据刷新入磁盘 1.fopen 返回文件类型的结构体的指针 包括三部分 1).文件描述符&#xff08;整形值 索引到磁盘文件&#xff09;…

【执行批处理后 executeBatch() 没反应,一个参数相信就能搞定】

一、场景是在使用EasyExcel读取全表时&#xff0c;每次手动提交事务6w多条&#xff0c;总计190w数据量的情况下发生的。 博主比较fw&#xff0c;卡住了两天&#x1f636; 此问题还有一个比较bug的地方&#xff0c;就是当你在 executeBatch() 上下打断点时还能够执行出来&…

目标检测——R-CNN算法解读

论文&#xff1a;Rich feature hierarchies for accurate object detection and semantic segmentation 作者&#xff1a;Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik 链接&#xff1a;https://arxiv.org/abs/1311.2524 代码&#xff1a;http://www.cs.berke…

信创之国产浪潮电脑+统信UOS操作系统体验8:安装Docker并进行测试验证scratch镜像

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、前言 今日在进行Docker容器相关知识的学习&#xff0c;不过学习环境都不是基于统信UOS操作系统的&#xff0c;为了实验&#xff0c;老猿觉得手头国产浪潮电脑统信UOS操作系统就是原生的linux操作系统&a…

【SparkSQL】基础入门(重点:SparkSQL和Hive的异同、SparkSQL数据抽象)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Spark SQL的定义、特点、发展历史、与hive的区别、数据抽象、SparkSession对象。 后续会继续分享其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一…

用flutter 写一个专属于儿子的听书的app

背景: 儿子最近喜欢上了用儿童手表听故事&#xff0c;但是手表边里的应用免费内容很少&#xff0c;会员一年要300多&#xff0c;这么一笔巨款&#xff0c;怎能承担的起&#xff0c;所以打算自己开发一个专属于儿子的听书app。 最终效果&#xff1a; 架构&#xff1a; 后端由两…