0%

本文所有方式均通过亲自验证

刻舟求剑是什么意思,今天算是认识到了。
为什么信息越来越多,我们却越来越累了呢,是因为有其他的东西吸引了我们本来的注意力吗?有这种原因,但还有更重要的是,有效信息太少。
今天我在网上找hexo显示本地图片的方式,尝试了很多都有问题,搜索出来的结果绝大部分都是无效信息。
下面我们说一说这些无效信息。

npm install hexo-asset-image
安装这个插件之后,显示图片不对。通过查看public目录下生成的index,发现是生成的图片link不对。
我们用上面这个命令安装的是hexo-asset-image 1.0.0版本,这个版本跟hexo3兼容上有问题,无法正确生成图片link。

npm install https://github.com/CodeFalling/hexo-asset-image –save
有的blog发现了这个问题,提示要用这个版本的插件0.0.5。
但这个插件也有问题,我们需要修改hexo-asset-image的index.js文件,可以参考hexo 图片路径错误/.com//
也就是说,用很老的版本是可以的,但同样的,这个老版本的插件跟hexo3及以上版本也存在兼容问题。

上面是网络搜索的两个主要结果,其实主要结果是第1个,第2个我也是在segmentfault上找到的。

还好,搜索引擎给了我一个更好的答案。

hexo原生支持image

关于hexo博客图片插件问题

hexo文档:资源文件夹

根据官方文档的说明,目前版本的hexo可以直接显示图片,主要有两种方式

  1. source/images文件夹下面放置图片,通过![](/images/sample.jpg)直接显示
  2. 通过文章资源文件夹
    1. _config.yml中 post_asset_folder : true
    2. hexo n title “name” 会生成同名的md和文件夹
    3. 把图片资源放置到对应的文件夹,这时候有两种方式来显示图片
      markdown方式: ![](1.png) 这种方式会导致的结果是,在首页中看不到图片,但如果点开具体文章,图片是可以正常显示的。
      正确方式:hexo3支持的标签用法

所以,原来的很多具体的处理措施,现在并不可用了,但方法该是我们掌握的,比如这里遇到无法显示的问题,首先要排查生成的html里image link是否正确,然后最好的方式是寻找官方文档的支持,说实话,现在搜索引擎的权重设计的并不好,很多已经无法用的方案,还在置顶,不知道因为作者是做了SEO优化,还是说搜索引擎本身并不智能到判断这些信息已经是不合适的了。

缘木求鱼,刻舟求剑。从古至今,大的道理还是那么多,关键是如何变成自己的模型。

就是启动hexo-asset-image之后,要修改index.js,另外记住,因为node-modules并不在git上,所以每次到了一个新的环境,你必须重新下载node-modules,并且修改index.js文件。切记!切记!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
'use strict';
var cheerio = require('cheerio');

// http://stackoverflow.com/questions/14480345/how-to-get-the-nth-occurrence-in-a-string
function getPosition(str, m, i) {
return str.split(m, i).join(m).length;
}

var version = String(hexo.version).split('.');
hexo.extend.filter.register('after_post_render', function(data){
var config = hexo.config;
if(config.post_asset_folder){
var link = data.permalink;
if(version.length > 0 && Number(version[0]) == 3)
var beginPos = getPosition(link, '/', 1) + 1;
else
var beginPos = getPosition(link, '/', 3) + 1;
// In hexo 3.1.1, the permalink of "about" page is like ".../about/index.html".
var endPos = link.lastIndexOf('/') + 1;
link = link.substring(beginPos, endPos);

var toprocess = ['excerpt', 'more', 'content'];
for(var i = 0; i < toprocess.length; i++){
var key = toprocess[i];

var $ = cheerio.load(data[key], {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false
});

$('img').each(function(){
if ($(this).attr('src')){
// For windows style path, we replace '\' to '/'.
var src = $(this).attr('src').replace('\\', '/');
if(!/http[s]*.*|\/\/.*/.test(src) &&
!/^\s*\//.test(src)) {
// For "about" page, the first part of "src" can't be removed.
// In addition, to support multi-level local directory.
var linkArray = link.split('/').filter(function(elem){
return elem != '';
});
var srcArray = src.split('/').filter(function(elem){
return elem != '' && elem != '.';
});
if(srcArray.length > 1)
srcArray.shift();
src = srcArray.join('/');
$(this).attr('src', config.root + link + src);
console.info&&console.info("update link as:-->"+config.root + link + src);
}
}else{
console.info&&console.info("no src attr, skipped...");
console.info&&console.info($(this));
}
});
data[key] = $.html();
}
}
});

前言

首先介绍一个可以在线查看C++转汇编的网站 www.godbolt.org,可以选择任意平台 任意版本的编译器,非常方便学习。

C++是一门需要跟底层打交道的语言,这里的底层不仅仅包括STL库,还包括汇编乃至指令集。

对上,C++要写出媲美java C#甚至python的代码,底层还要关心各种内存分配、指针,ABI兼容,出了错误,非常难以排查,不像“虚拟机语言”,只需要关注自己这一层。从这个角度看,C++语言的代码耦合度太高了。至少以下几个方面并不适合C++

1. 界面开发: 很多大厂的PC端用的是directUI架构,要想写好需要对C++熟练,而且要自己做各种精细控制。  
2. 小工具开发:胶水语言不香吗?  
3. 科学试验性质项目:比如机器学习模型训练  

其他:比如一般的程序开发,除非其它语言没有对应的库,否则都应该选用JAVA/C#/PYTHON这类语言开发,或者golang/rust。总之,C++的学习曲线太陡峭,但跟rust相比,历史包袱又很重。
既然这样,为什么我们还要学习C++呢?就是因为目前来说没有特别好能替代的,或许再过两年,rust和go成长起来之后,C++的市场份额会进一步萎缩,慢慢地被淘汰掉吧,淘汰也不是仅仅靠喊就行的,新语言还是得加油努力喔。

在Linux下我们用gdb进行代码调试和crash分析。在Windows平台我们有很多工具,一般是用IDA进行静态反汇编,用OD或者WinDbg进行动态分析,WinDbg也可以反汇编。
现在写blog的平台是Linux,所以先写linux的。我们的重点放在分析crashdump上。

gdb crashdump分析

生成dump的方式很简单就不用说了,ulimit -c unlimited,不过这里生成的是默认名称,为了方便保存且与代码对应,可以做些修改,每次生成新的名字。

一般发布版本,要从几个方面考虑

  1. 可追溯
  2. 自动化
  3. 防破解

这里,我们提到的是可追溯。每次发布版本要在git上有对应的commit,而这个commit要很容易就从SDK里获得,比如在加载时输出commit-id。

分析堆栈

Read more »

音素与音频

最近两年做MetaHuman的人越来越多了,而MetaHuman涉及到肢体的驱动和嘴部的驱动,嘴型的驱动又依靠声音。

声音驱动表情(包括口型)有很多方案,比较著名的是微软的视素驱动,获取 lip-sync 的人脸姿态事件,但好像根据语言也会有不同的视素,所以最终还是跟音素相关。

现在很多语音厂商都能够同时提供音频对应的音素了,比如AiSpeech,Data Baker,但拿到音频和音素之后,我们先要想要的是 ———— 如何判断音素和音频在时间维度上对应的准确度。

为了解决这个问题,我们先来介绍一个工具和一种格式。

TextGrid格式

TextGrid一种专门对声音进行标注的文本格式,里面可以记录很多信息 Praat scripting 入门 (2) TextGrid 和录音管理

我一般用来标准音素时间戳,大概格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
File type = "ooTextFile"
Object class = "TextGrid"

xmin = 0
xmax = 2.3
tiers? <exists>
size = 3
item []:
item [1]:
class = "IntervalTier"
name = "sentence"
xmin = 0
xmax = 2.3
intervals: size = 1
intervals [1]:
xmin = 0
xmax = 2.3
text = "říkej ""ahoj"" dvakrát"
item [2]:
class = "IntervalTier"
name = "phonemes"
xmin = 0
xmax = 2.3
intervals: size = 3
intervals [1]:
xmin = 0
xmax = 0.7
text = "r̝iːkɛj"
intervals [2]:
xmin = 0.7
xmax = 1.6
text = "ʔaɦɔj"
intervals [3]:
xmin = 1.6
xmax = 2.3
text = "dʋakraːt"
item [3]:
class = "TextTier"
name = "bell"
xmin = 0
xmax = 2.3
points: size = 2
points [1]:
number = 0.9
mark = "ding"
points [2]:
number = 1.3
mark = "dong"

我们拿到厂商提供的音素之后,可以保存成这样的文件 一个开源的TextGrid读写器,此时,我们手中有了音频和对应的TextGrid文件。

当然生成textgrid的代码也可以参考kaldi代码,kaldi是语音界的天花板和军火库,非常多的人从里面汲取营养,以后有兴趣或需要的时候,可以参考这个工程,后面我会单独写一篇文章,讨论如何使用kaldi,实现从音频到嘴型驱动的全流程。

回到本文,有了audio和textgrid文件之后,该怎么把这两者结合起来使用呢?

Praat

Praat

Praat是语音界的一个神奇工具,而我只是用它来实现音素时间戳校准的工作。

因为手头没有对应的音频文件和TextGrid(后面补齐),接下来的步骤是

  • 出现的界面中有两个文件,同时选中,就会在窗口右侧出现Edit and Continue按钮,此时点击这个按钮,就会出现一个界面,这个界面是上面是音频,下面是音素时间戳,每个音素时间戳占用1格(格子有大有小,跟duration有关),我们点击这个格子,就能提到这段格子所对应的音频,假设这个格子上标注的是 ang,但听到的声音明显不仅仅包含ang,比如杨 i ang2,如果我们听到了 i的声音,那么我们就可以证明,这个音素时间戳太靠前了。

前言

在前面一篇中,我们认识了delegate,具体内容可以看上一篇。

前两天遇到一个crash,就是提示我 Slate can only be accessed from the GameThread or the SlateLoadingThread!

具体操作是—— 我绑定了event到delegate,在event里我会对UI进行操作,结果就是crash报上面的错误。

根据提示信息,在broadcast()的位置调用IsGameThread()发现当前线程不是GameThread,而UE要求对UI的操作必须在GameThread线程,否则会直接crash。(从这里也能看出来,UE引擎是很严密的,正是这个严密,才给了我们很大的自由度)。

解决思路就是把UI操作放到GameThread里,我用的是AsyncTask方法,这个大家上网查一下就可以了,这里不再赘述。我想谈论的是delegate在broadcast() 或者 ExecuteIfBound() 的时候,到底是一个什么样的流程,是在哪个线程中执行的?是异步执行吗?多broadcast()来说,多个绑定的event执行有先后顺序吗?

DELEGATE宏定义及其展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#define DECLARE_DYNAMIC_MULTICAST_DELEGATE( DelegateName ) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_DELEGATE( FWeakObjectPtr, DelegateName, DelegateName##_DelegateWrapper, , FUNC_CONCAT( *this ), void )

#define FUNC_DECLARE_DYNAMIC_MULTICAST_DELEGATE(TWeakPtr, DynamicMulticastDelegateClassName, ExecFunction, FuncParamList, FuncParamPassThru, ...) \
class DynamicMulticastDelegateClassName : public TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__> \
{ \
public: \
/** Default constructor */ \
DynamicMulticastDelegateClassName() \
{ \
} \
\
/** Construction from an FMulticastScriptDelegate must be explicit. This is really only used by UObject system internals. */ \
explicit DynamicMulticastDelegateClassName( const TMulticastScriptDelegate<>& InMulticastScriptDelegate ) \
: TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__>( InMulticastScriptDelegate ) \
{ \
} \
\
/** Broadcasts this delegate to all bound objects, except to those that may have expired */ \
void Broadcast( FuncParamList ) const \
{ \
ExecFunction( FuncParamPassThru ); \
} \
};


/**
* Script multi-cast delegate base class
*/
template <typename TWeakPtr = FWeakObjectPtr>
class TMulticastScriptDelegate
{
public:

/**
* Default constructor
*/
inline TMulticastScriptDelegate() { }

public:

/**
* Checks to see if any functions are bound to this multi-cast delegate
*
* @return True if any functions are bound
*/
inline bool IsBound() const
{
UE_DELEGATES_MT_SCOPED_READ_ACCESS(AccessDetector);

return InvocationList.Num() > 0;
}

/**
* Checks whether a function delegate is already a member of this multi-cast delegate's invocation list
*
* @param InDelegate Delegate to check
* @return True if the delegate is already in the list.
*/
bool Contains( const TScriptDelegate<TWeakPtr>& InDelegate ) const
{
UE_DELEGATES_MT_SCOPED_READ_ACCESS(AccessDetector);

return InvocationList.Contains( InDelegate );
}

/**
* Checks whether a function delegate is already a member of this multi-cast delegate's invocation list
*
* @param InObject Object of the delegate to check
* @param InFunctionName Function name of the delegate to check
* @return True if the delegate is already in the list.
*/
bool Contains( const UObject* InObject, FName InFunctionName ) const
{
UE_DELEGATES_MT_SCOPED_READ_ACCESS(AccessDetector);

return InvocationList.ContainsByPredicate( [=]( const TScriptDelegate<TWeakPtr>& Delegate ){
return Delegate.GetFunctionName() == InFunctionName && Delegate.IsBoundToObjectEvenIfUnreachable(InObject);
} );
}

/**
* Adds a function delegate to this multi-cast delegate's invocation list
*
* @param InDelegate Delegate to add
*/
void Add( const TScriptDelegate<TWeakPtr>& InDelegate )
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

// First check for any objects that may have expired
CompactInvocationList();

// Add the delegate
AddInternal( InDelegate );
}

/**
* Adds a function delegate to this multi-cast delegate's invocation list if a delegate with the same signature
* doesn't already exist in the invocation list
*
* @param InDelegate Delegate to add
*/
void AddUnique( const TScriptDelegate<TWeakPtr>& InDelegate )
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

// Add the delegate, if possible
AddUniqueInternal( InDelegate );

// Then check for any objects that may have expired
CompactInvocationList();
}

/**
* Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the
* order of the delegates may not be preserved!
*
* @param InDelegate Delegate to remove
*/
void Remove( const TScriptDelegate<TWeakPtr>& InDelegate )
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

// Remove the delegate
RemoveInternal( InDelegate );

// Check for any delegates that may have expired
CompactInvocationList();
}

/**
* Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the
* order of the delegates may not be preserved!
*
* @param InObject Object of the delegate to remove
* @param InFunctionName Function name of the delegate to remove
*/
void Remove( const UObject* InObject, FName InFunctionName )
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

// Remove the delegate
RemoveInternal( InObject, InFunctionName );

// Check for any delegates that may have expired
CompactInvocationList();
}

/**
* Removes all delegate bindings from this multicast delegate's
* invocation list that are bound to the specified object.
*
* This method also compacts the invocation list.
*
* @param InObject The object to remove bindings for.
*/
void RemoveAll(const UObject* Object)
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

for (int32 BindingIndex = InvocationList.Num() - 1; BindingIndex >= 0; --BindingIndex)
{
const TScriptDelegate<TWeakPtr>& Binding = InvocationList[BindingIndex];

if (Binding.IsBoundToObject(Object) || Binding.IsCompactable())
{
InvocationList.RemoveAtSwap(BindingIndex);
}
}
}

/**
* Removes all functions from this delegate's invocation list
*/
void Clear()
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

InvocationList.Empty();
}

/**
* Converts this delegate to a string representation
*
* @return Delegate in string format
*/
template <typename UObjectTemplate>
inline FString ToString() const
{
UE_DELEGATES_MT_SCOPED_READ_ACCESS(AccessDetector);

if( IsBound() )
{
FString AllDelegatesString = TEXT( "[" );
bool bAddComma = false;
for( typename FInvocationList::TConstIterator CurDelegate( InvocationList ); CurDelegate; ++CurDelegate )
{
if (bAddComma)
{
AllDelegatesString += TEXT( ", " );
}
bAddComma = true;
AllDelegatesString += CurDelegate->template ToString<UObjectTemplate>();
}
AllDelegatesString += TEXT( "]" );
return AllDelegatesString;
}
return TEXT( "<Unbound>" );
}

/** Multi-cast delegate serialization */
friend FArchive& operator<<( FArchive& Ar, TMulticastScriptDelegate<TWeakPtr>& D )
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(D.AccessDetector);

if( Ar.IsSaving() )
{
// When saving the delegate, clean up the list to make sure there are no bad object references
D.CompactInvocationList();
}

Ar << D.InvocationList;

if( Ar.IsLoading() )
{
// After loading the delegate, clean up the list to make sure there are no bad object references
D.CompactInvocationList();
}

return Ar;
}

friend void operator<<(FStructuredArchive::FSlot Slot, TMulticastScriptDelegate<TWeakPtr>& D)
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(D.AccessDetector);

FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive();

if (UnderlyingArchive.IsSaving())
{
// When saving the delegate, clean up the list to make sure there are no bad object references
D.CompactInvocationList();
}

Slot << D.InvocationList;

if (UnderlyingArchive.IsLoading())
{
// After loading the delegate, clean up the list to make sure there are no bad object references
D.CompactInvocationList();
}
}

/**
* Executes a multi-cast delegate by calling all functions on objects bound to the delegate. Always
* safe to call, even if when no objects are bound, or if objects have expired. In general, you should
* never call this function directly. Instead, call Broadcast() on a derived class.
*
* @param Params Parameter structure
*/
template <class UObjectTemplate>
void ProcessMulticastDelegate(void* Parameters) const
{
UE_DELEGATES_MT_SCOPED_WRITE_ACCESS(AccessDetector);

if( InvocationList.Num() > 0 )
{
// Create a copy of the invocation list, just in case the list is modified by one of the callbacks during the broadcast
typedef TArray< TScriptDelegate<TWeakPtr>, TInlineAllocator< 4 > > FInlineInvocationList;
FInlineInvocationList InvocationListCopy = FInlineInvocationList(InvocationList);

// Invoke each bound function
for( typename FInlineInvocationList::TConstIterator FunctionIt( InvocationListCopy ); FunctionIt; ++FunctionIt )
{
if( FunctionIt->IsBound() )
{
// Invoke this delegate!
FunctionIt->template ProcessDelegate<UObjectTemplate>(Parameters);
}
else if ( FunctionIt->IsCompactable() )
{
// Function couldn't be executed, so remove it. Note that because the original list could have been modified by one of the callbacks, we have to search for the function to remove here.
RemoveInternal( *FunctionIt );
}
}
}
}

从宏定义上看,我们声明一个DELEGATE的时候,实际上是声明了一个类,这个类向上的父类传递依次是 TBaseDynamicMulticastDelegate 、TMulticastScriptDelegate。

TMulticastScriptDelegate类里的接口包括绑定接口 Add 、AddUnique,实际执行的时候,我们看到broadcast接口在TBaseDynamicMulticastDelegate 里,从宏定义上看是直接执行了

1
2
3
4
voidBroadcast( FuncParamList )const
{
ExecFunction( FuncParamPassThru );
}

那这里的ExecFunction是绑定好的event or function?FuncParamPassThru是传递的参数?

先说结论,broadcast是阻塞同步操作,所有bounded event不是并行执行,是根据存在InvocationList 中的顺序执行,看 ProcessMulticastDelegate() 接口的注释就能明白了。

但还是有些糊涂,broadcast是怎么跟 ProcessMulticastDelegate 联系起来的呢。

我们把宏定义展开就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define DECLARE_DYNAMIC_MULTICAST_DELEGATE( DelegateName ) \
CURRENT_FILE_ID##_##__LINE__##_DELEGATE \
class DynamicMulticastDelegateClassName : public TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__> \
{\
public: \
/** Default constructor */ \
DynamicMulticastDelegateClassName() \
{ \
} \
\
/** Construction from an FMulticastScriptDelegate must be explicit. This is really only used by UObject system internals. */ \
explicit DynamicMulticastDelegateClassName( const TMulticastScriptDelegate<>& InMulticastScriptDelegate ) \
: TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__>( InMulticastScriptDelegate ) \
{ \
} \
\
/** Broadcasts this delegate to all bound objects, except to those that may have expired */ \
void Broadcast( FuncParamList ) const \
{ \
ExecFunction( FuncParamPassThru ); \
} \
|

这里我们发现几个问题

CURRENT_FILE_ID##_##LINE##_DELEGATE 在类声明开头有什么用途?
FUNC_DECLARE_DYNAMIC_MULTICAST_DELEGATE(TWeakPtr, DynamicMulticastDelegateClassName, ExecFunction, FuncParamList, FuncParamPassThru, …)里 ExecFuction FunctionParamlist FuncParamPassThru,我似乎之后到这几个参数是什么意思,但它们从何而来?展开之后编译难道不会找不到定义而报错吗?
这个问题要继续探究一下,所以本文后面会再做修订。

回来了,继续更新。

从网上找了些资料,发现一个工具 UHT(Unreal Header Tool)用来扫描 .h文件里的UFUNCTION UCLASS以及其它的一些宏定义,来生成相应的代码。关于UHT的作用,估计得新开一章,这里暂时就知道UHT会生成我们上面所提到的宏就行了。

下面我们举例子来说。

新建一个动态多播代理

1
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTestDelegate);

用UHT工具会生成如下代码

1
2
3
4
5
6
7
8
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h

#define FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h_9_DELEGATE \
static inline void FTestDelegate_DelegateWrapper(const FMulticastScriptDelegate& TestDelegate) \
{ \
TestDelegate.ProcessMulticastDelegate<UObject>(NULL); \
}

这里 BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,LINE,_DELEGATE) 的定义就是 FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h_9_DELEGATE,本质上就是一个处理绑定函数的静态方法。

这样上面我们感觉缺失的参数 DelegateName##_DelegateWrapper 在这里就找到定义了。

这样下来,我们的宏全部展开后就成了下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define FUNC_CONCAT( ... ) __VA_ARGS__

#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h

#define FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h_9_DELEGATE \
static inline void FTestDelegate_DelegateWrapper(const FMulticastScriptDelegate& TestDelegate) \
{ \
TestDelegate.ProcessMulticastDelegate<UObject>(NULL); \
}
#define DECLARE_DYNAMIC_MULTICAST_DELEGATE( FTestDelegate ) \
static inline void FTestDelegate_DelegateWrapper(const FMulticastScriptDelegate& TestDelegate) \
{ \
TestDelegate.ProcessMulticastDelegate<UObject>(NULL); \
} \

class FTestDelegate: public TBaseDynamicMulticastDelegate<FWeakObjectPtr, __VA_ARGS__> \
{ \
public: \
/** Default constructor */ \
FTestDelegate() \
{ \
} \
\
/** Construction from an FMulticastScriptDelegate must be explicit. This is really only used by UObject system internals. */ \
explicit FTestDelegate( const TMulticastScriptDelegate<>& InMulticastScriptDelegate ) \
: TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__>( InMulticastScriptDelegate ) \
{ \
} \
\
/** Broadcasts this delegate to all bound objects, except to those that may have expired */ \
void Broadcast( FuncParamList ) const \
{ \
FTestDelegate_DelegateWrapper( FUNC_CONCAT( *this ) ); \
} \
};

__VA_ARGS__ 是C/C++宏中的特殊标识符,用于表示宏参数的可变参数列表,此时 __VA_ARGS__ 所代表的参数只有 *this,而 #define FUNC_CONCAT( … ) __VA_ARGS__ 的意思是 FUNC_CONCAT 宏 是将 宏参数 展开为逗号分隔的列表,所以此时再进一步替换为如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define FUNC_CONCAT( ... ) __VA_ARGS__

#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h

#define FID_AnimationSample_Source_AnimationSample_AnimationSampleCharacter_h_9_DELEGATE \
static inline void FTestDelegate_DelegateWrapper(const FMulticastScriptDelegate& TestDelegate) \
{ \
TestDelegate.ProcessMulticastDelegate<UObject>(NULL); \
}
#define DECLARE_DYNAMIC_MULTICAST_DELEGATE( FTestDelegate ) \
static inline void FTestDelegate_DelegateWrapper(const FMulticastScriptDelegate& TestDelegate) \
{ \
TestDelegate.ProcessMulticastDelegate<UObject>(NULL); \
} \

class FTestDelegate: public TBaseDynamicMulticastDelegate<FWeakObjectPtr, *this> \
{ \
public: \
/** Default constructor */ \
FTestDelegate() \
{ \
} \
\
/** Construction from an FMulticastScriptDelegate must be explicit. This is really only used by UObject system internals. */ \
explicit FTestDelegate( const TMulticastScriptDelegate<>& InMulticastScriptDelegate ) \
: TBaseDynamicMulticastDelegate<TWeakPtr, __VA_ARGS__>( InMulticastScriptDelegate ) \
{ \
} \
\
/** Broadcasts this delegate to all bound objects, except to those that may have expired */ \
void Broadcast( ) const \
{ \
FTestDelegate_DelegateWrapper( *this ); \
} \
};

到这里为止,从代码层面看,完全都说得通了。

但还有几个遗留问题

FuncParamList 和 FuncParamPassThru 有什么区别?
对UHT更深入地理解
其它未想到的问题
后面希望能出一篇关于UHT的文章,结合UHT源码,剖析架构。

其它问题就等想起来再说了。

最近遇到一个问题,把某些文件夹添加到 .gitignore,但git add的时候却还是添加进去了,不生效。

原因是,gitignore只针对untracked files。

如果是tracked files(之前已经commit过),那么修改 .gitignore不能在 add的时候忽略掉tracked files。

但如果我们的工作区还需要这些文件,那该怎么办?我们肯定是不希望在本地重新生成的,太浪费时间了。

1
2
3
git rm -r --cached tempFile
git commit -m "从版本库移除 tempFile"
git push

当我们需要删除暂存区或分支上的文件,同时工作区不需要这个文件,可以使用 git rm

1
2
3
git rm file
git commit -m "delete file"
git push

当我们需要删除暂存区或分支上的文件,但是工作区需要这个文件,可以使用 git rm –cached