[{"data":1,"prerenderedAt":1136},["ShallowReactive",2],{"blog-page-12":3,"blog-count":1135},[4,310,381,540,988],{"id":5,"title":6,"body":7,"date":288,"description":13,"extension":289,"meta":290,"navigation":293,"path":305,"seo":306,"stem":307,"tags":308,"__hash__":309},"blogs\u002F_legacy\u002F2018\u002F2018-09-08-blog-moving-prepare.md","Blog迁移准备中",{"type":8,"value":9,"toc":276},"minimark",[10,14,17,21,24,27,30,33,36,43,46,51,54,57,60,63,66,69,72,75,78,81,92,95,100,103,106,109,112,118,121,127,130,136,139,142,145,151,154,160,163,167,170,176,179,185,188,191,202,205,208,211,214,217,222,225,228,233,236,242,245,249,252,258,261,267,270,273],[11,12,13],"p",{},"最近不知为何托管的主机商那边似乎宕机了比较长的时间的样子。",[11,15,16],{},"原本在主机这一块，一直都没打算再去折腾。因为没有以前那么多时间来照顾这方面了，所以前段发现主机经常访问不稳定的时候，也没有太过于在意，只是将域名托管到了CloudFlare。",[18,19,20],"h2",{"id":20},"问题依旧",[11,22,23],{},"CloudFlare这边虽然访问速度会有所下降，但是至少能够提供一定的稳定性保证。毕竟这边虽然一直有在更新，但是每天的访问量其实并不高，基本都是靠搜索引擎的自然流量。所以偶尔访问不稳定的话，也不会有什么太大的问题。",[11,25,26],{},"但是没想到似乎还是出了些问题，这周连续好几天回家之后发现怎么都连不上网站，虽然连不上是常见的状况，但连续无法访问还是有些在意。",[11,28,29],{},"然后，发现现在的网络环境真的是和以前完全不一样了。",[11,31,32],{},"首先，一个比较常见和还算可以理解的一点，就是CloudFlare对网络状况的虚报。基本上CloudFlare这边看到的访问量和Google Analytics的数据比起来会多出一些。",[11,34,35],{},"但是问题是，实际从9月1日起，网站这边在很多区域就已经进入完全无法访问的状态了，可以从GA的数据看到：",[11,37,38],{},[39,40],"img",{"alt":41,"src":42},"image","\u002Fwp-content\u002Fuploads\u002F2018\u002F09\u002Fimage_thumb.png",[11,44,45],{},"但是同期CloudFlare的数据是这样的",[11,47,48],{},[39,49],{"alt":41,"src":50},"\u002Fwp-content\u002Fuploads\u002F2018\u002F09\u002Fimage_thumb-1.png",[11,52,53],{},"完全没有看到网站的可访问性出了问题，仿佛一切都是那么的美好。",[11,55,56],{},"另外比较惊讶的一点是，Google WebMaster不知什么时候改版了，目前的WebMaster是不会通知你网站已经宕掉的。所以像我一样，依赖于WebMaster的宕机通知来了解网站情况的童鞋们一定要留意了。",[11,58,59],{},"起初有怀疑是CloudFlare这边的问题，所以中途将NS记录重新指向了DNSPod。但是过了一两天发现还是有问题，于是直接修改Hosts，发现果然还是虚拟主机商挂掉了……",[11,61,62],{},"怎么说呢，这个问题还是比较厉害的。由于是老牌主机商了，所以当时也没在意。后面转移网站数据的时候发现，最新的数据似乎丢失了。估计应该是主机商那边连续性的遇到了什么问题。但是现在每天都没有什么时间来搞网站这方面，更加没有精力再到处去找新主机商。原本也打算就这样算了，但是没想到主机商那边也是从头到尾没有一点反应。想来或许是认为会在虚拟主机商这里托管的其实都是些无人问津的小站，所以站长不主动去敲客服，就会假装什么都没有发生过。这样的态度难免让人有些心寒，搞得好像是贪便宜将网站挂到虚拟主机商这边的自己的不对一样了。",[11,64,65],{},"诚然会挂虚拟主机商的网站大抵都没有好好的在进行商业运营，很多网站最多坚持个一两年，等域名到期了也就自然消灭了。因此虚拟主机商这边都会有很严重的超卖现象，就算偶尔出现几个小时的无法访问，说实话也很少会被注意到。但是我认为这样的服务态度是不行的，会自己花精神去买域名、买空间、搭网站、然后去写些什么，且不论之后会不会放弃，至少站长本人在这件事情上是付出了真心的努力的。出现了如此严重的连续宕机，甚至数据都是好久之后才能恢复的情况，连一封告知都没有，就这样假装岁月静好实在是让人无法接受。",[11,67,68],{},"因此，我打算重新迁徙到别的地方去，但是现在真的没有时间精力去找空间了，所以估计会花费比较长的时间。好在手里有一张腾讯云的优惠券，于是干脆的先跑到腾讯云上避难了。",[18,70,71],{"id":71},"迁移记录",[11,73,74],{},"为了方便下次迁移时能够快速的动作，而不是像这次一样要重新去翻以前的笔记以及到处搜索，将这次的过程完全记录了下来。",[11,76,77],{},"首先还是使用最习惯的Ubuntu Lts，当前版本是16。",[11,79,80],{},"开通之后用Winscp接入putty直接登录，然后首先设定su密码。",[82,83,88],"pre",{"className":84,"code":86,"language":87},[85],"language-text","sudo passwd\n","text",[89,90,86],"code",{"__ignoreMap":91},"",[11,93,94],{},"之后直接su，这样虽然有些不符合规范，但是确实比较方便。",[96,97,99],"h3",{"id":98},"lnmp","LNMP",[11,101,102],{},"接着就是lnmp的安装，虽然之前比较习惯与用别人分享的脚本，但是一来不是马上能找到。二来有的脚本是要自己下载源码进行编译的，有的会给出很多选择。",[11,104,105],{},"但是我现在不想选择，我只想说，「一番いいのを頼む 」。",[11,107,108],{},"反正我就架个WordPress，顶多接入个Phalcon，有必要选那么多吗？直接上版本号最高的官方编译版就好了~",[11,110,111],{},"首先，违反常识的先装php，因为要用nginx，所以不直接装php，而是装php-fpm，要不然会给你带个Apache。",[82,113,116],{"className":114,"code":115,"language":87},[85],"add-apt-repository ppa:ondrej\u002Fphp\napt-get update\napt-get install php-fpm\n",[89,117,115],{"__ignoreMap":91},[11,119,120],{},"然后上nginx",[82,122,125],{"className":123,"code":124,"language":87},[85],"add-apt-repository ppa:nginx\u002Fdevelopment\napt install software-properties-common\napt-get update\napt-get install nginx\n\n",[89,126,124],{"__ignoreMap":91},[11,128,129],{},"最后我也不知道MySql哪个版本好，干脆上了还有印象的MariaDB。",[82,131,134],{"className":132,"code":133,"language":87},[85],"sudo apt-key adv --recv-keys --keyserver hkp:\u002F\u002Fkeyserver.ubuntu.com:80 0xF1656F24C74CD1D8\nsudo add-apt-repository 'deb [arch=amd64,i386,ppc64el] http:\u002F\u002Fftp.yz.yamagata-u.ac.jp\u002Fpub\u002Fdbms\u002Fmariadb\u002Frepo\u002F10.2\u002Fubuntu xenial main'\n\napt update\napt install mariadb-server\n",[89,135,133],{"__ignoreMap":91},[11,137,138],{},"装完以上这些，直接访问服务器IP应该就能看到欢迎页面了。再稍微写个phpinfo()的测试页面就知道php正不正常了。",[11,140,141],{},"一般情况下默认配置的nginx基本不可能挂掉，顶多就是php解析有问题。这个时候直接去改nginx的默认配置文件就可以了，在\u002Fetc\u002Fnginx里面。",[11,143,144],{},"不需要急着去查资料，基本上nginx官方已经给写好了，只要按照注释里面的提示去掉一些注释就可以了。改完记得重启服务，配置不对基本就重启不了~",[82,146,149],{"className":147,"code":148,"language":87},[85],"service nginx restart\n",[89,150,148],{"__ignoreMap":91},[11,152,153],{},"比较容易出问题的是php-fpm的名称，很容易坑，因为安装的时候叫php-fpm，但是实际使用时却是：",[82,155,158],{"className":156,"code":157,"language":87},[85],"fastcgi_pass unix:\u002Fvar\u002Frun\u002Fphp\u002Fphp7.2-fpm.sock;\n",[89,159,157],{"__ignoreMap":91},[11,161,162],{},"这个真的很坑，因为nginx的配置里面没有说明，看命名方式似乎会跟着php的版本变？",[96,164,166],{"id":165},"wordpress","Wordpress",[11,168,169],{},"这个直接下载解压了，WordPress是业界知名安装简单的，虽然迁移网站会有些麻烦，不过也只是移动文件而已。",[82,171,174],{"className":172,"code":173,"language":87},[85],"wget https:\u002F\u002Fwordpress.org\u002Flatest.tar.gz\ntar -xzvf latest.tar.gz\n",[89,175,173],{"__ignoreMap":91},[11,177,178],{},"为了方便，首先将文件夹权限设一下",[82,180,183],{"className":181,"code":182,"language":87},[85],"chown www-data.www-data . -R\n",[89,184,182],{"__ignoreMap":91},[11,186,187],{},"这里有个问题，为了方便其实我的目录权限其实777的，但是有的时候对于wordpress还是不足够。",[11,189,190],{},"有时候PHP的disable func也会导致问题，不过这次并没有遇到。",[11,192,193,194,201],{},"然后就是自己建立数据库了，虽然CPanel的主机商比较爱装phpMyAdmin，但是自己用的话推荐这个[",[195,196,200],"a",{"href":197,"rel":198},"https:\u002F\u002Fwww.adminer.org\u002F#download",[199],"nofollow","adminer","]。一个文件搞定一切~",[11,203,204],{},"操作上也比较简明，有了数据库装WordPress就是简单的五步了。",[11,206,207],{},"WordPress现在的版本好像对目录权限要求有点高，主要是也不会出什么问题，就是网站访问速度会变慢……所以趁早chown比较好。",[96,209,210],{"id":210},"数据迁移",[11,212,213],{},"数据迁移就是搬，基本不会有问题。不过这次是由https环境搬到http环境，所以之前的一些配置造成了问题，这里全部回退掉",[11,215,216],{},"首先",[11,218,219],{},[39,220],{"alt":41,"src":221},"\u002Fwp-content\u002Fuploads\u002F2018\u002F09\u002Fimage_thumb-2.png",[11,223,224],{},"这个必须改回来，要不然会自动跳到https上。",[11,226,227],{},"还有这个：",[11,229,230],{},[39,231],{"alt":41,"src":232},"\u002Fwp-content\u002Fuploads\u002F2018\u002F09\u002Fimage_thumb-3.png",[11,234,235],{},"以及将之前替换掉的文章内的图片的引用换回来，在adminer里面执行sql语句就好：",[82,237,240],{"className":238,"code":239,"language":87},[85],"UPDATE wp_posts SET 'post_content' = REPLACE ('post_content', 'src=\"https:\u002F\u002Fblog.ch-wind.com', 'src=\"\u002F');\nUPDATE wp_posts SET 'post_content' = REPLACE ('post_content', 'href=\"https:\u002F\u002Fblog.ch-wind.com', 'src=\"href:\u002F\u002Fblog.ch-wind.com');\n\n",[89,241,239],{"__ignoreMap":91},[11,243,244],{},"以及由于主题是自己写的，所以randompic.php里面的写死的链接也要改一下",[96,246,248],{"id":247},"windowlivewriter","WindowLiveWriter",[11,250,251],{},"之后就是最后一个坑，没想到wlw竟然无法提交文章。不停的给我报",[253,254,255],"blockquote",{},[11,256,257],{},"parse error. not well formed",[11,259,260],{},"错误代号-32700……起初以为是WordPress的新bug，但是在本地装好环境发现没问题。最后几经波折才找到，是php-xml没有装……真想吐槽：你报错能智能点吗",[82,262,265],{"className":263,"code":264,"language":87},[85],"apt-get install php-xml\n",[89,266,264],{"__ignoreMap":91},[11,268,269],{},"这样就解决了。",[18,271,272],{"id":272},"避难完成",[11,274,275],{},"这样将文章推到blog上，避难就算完成了。之后要找个好的停放地点才行。",{"title":91,"searchDepth":277,"depth":278,"links":279},2,3,[280,281,287],{"id":20,"depth":277,"text":20},{"id":71,"depth":277,"text":71,"children":282},[283,284,285,286],{"id":98,"depth":278,"text":99},{"id":165,"depth":278,"text":166},{"id":210,"depth":278,"text":210},{"id":247,"depth":278,"text":248},{"id":272,"depth":277,"text":272},"2018-09-08","md",{"layout":291,"status":292,"published":293,"author":294,"author_login":296,"author_email":297,"wordpress_id":298,"wordpress_url":299,"date_gmt":300,"excerpt":301},"post","publish",true,{"display_name":295,"login":296,"email":297,"url":91},"风铃","flinkor","flinkor@foxmail.com",2560,"\u002F?p=2560","2018-09-08 15:37:20 +0000",{"type":8,"value":302},[303],[11,304,13],{},"\u002F2018-09-08-blog-moving-prepare",{"title":6,"description":13},"_legacy\u002F2018\u002F2018-09-08-blog-moving-prepare",[98,165],"zHkC7jMcJA1rlBDrF2aLCN5EdqAsGRALh_Y2-DSTaLI",{"id":311,"title":312,"body":313,"date":366,"description":317,"extension":289,"meta":367,"navigation":293,"path":376,"seo":377,"stem":378,"tags":379,"__hash__":380},"blogs\u002F_legacy\u002F2018\u002F2018-08-28-old-mans-journy.md","《Old Man’s Journy》",{"type":8,"value":314,"toc":364},[315,318,321,324,327,330,333,336,341,344,347,350,355,358,361],[11,316,317],{},"这个游戏出了有一段时间了，只是一直没有找到时间玩。",[11,319,320],{},"原本的话，这篇文章不是这样的，本来前几天趁着刚玩完游戏的余韵将感想写了出来。不料主机的Host商那边发生了点问题，不知为何到现在为止的数据都被回退了。",[11,322,323],{},"到了现在，我已经想不起来这到底是一款怎样的游戏了。并不是指的游戏的关卡模式、剧情这些，而是对于整个游戏本身的感动和共鸣。",[11,325,326],{},"《Old Man’s Journy》的出彩之处，在于叙事。关卡的推进和叙事的交互控制的非常好，让玩家可以渐渐的进入故事之中。",[11,328,329],{},"这大概是一个老水手的一生的故事，独自一人眺望大海的老水手，收到一封信之后突然的踏上了旅途。旅途虽然跌宕起伏，但是更加充满波澜的，是老水手所走过的人生。",[11,331,332],{},"或许，这是一个关于梦想的故事。开篇我们能看到，从邂逅到婚姻，一切都是那么美好。但忽而一切又变了，场景的天气也变为阴暗的雨季。到底是怎样呢？剧情上并没有详细的解释，因为通篇都没有台词。",[11,334,335],{},"所以也无法得知，老水手最后究竟有没有找到想要找的东西。",[11,337,338],{},[39,339],{"alt":91,"src":340},"https:\u002F\u002Fwww.gamebits.net\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Foldmansjourney-10a.jpg",[11,342,343],{},"对于那时年轻的水手而言，相信自己寻找的什么东西一定是存在的，跨上了环游世界的旅程，那是即便离开想念的家人也不得不实现的东西。",[11,345,346],{},"但是终归或许还是没有找到自己想要寻找的东西，我们只能看到，游戏的开篇，已经年老的水手独自在海角眺望着大海。",[11,348,349],{},"从几张CG提供的线索来看，水手或许是在偶然的情况下看到了传说中的生物，所以想要去追寻它的踪迹。所以即便后来无法再出航，也想要在海边建一个小屋来守望着大海。",[11,351,352],{},[39,353],{"alt":91,"src":354},"https:\u002F\u002Fwww.gamebits.net\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Foldmansjourney-13.jpg",[11,356,357],{},"游戏的过程，将老水手回家的旅程和老人人生的旅程重叠在了一起进行了叙述。由于没有什么文字线索，让人不由得担心在旅途的前端有什么在等着他。尤其中途落水的关卡中的细节处理，老水手变得有些半透明，让人有些担心不会他的旅途会不会就在这里结束了，或者之后的都是幻想式的展开了。",[11,359,360],{},"好在最后的结局虽然有些遗憾却也并非完全的负面结局，只是不知对于老水手本人而言，究竟又是如何呢？",[11,362,363],{},"我们无法点评他人的人生，又有谁知道什么才是最好的结局呢？",{"title":91,"searchDepth":277,"depth":278,"links":365},[],"2018-08-28",{"layout":291,"status":292,"published":293,"author":368,"author_login":296,"author_email":297,"wordpress_id":369,"wordpress_url":370,"date_gmt":371,"excerpt":372},{"display_name":295,"login":296,"email":297,"url":91},2550,"\u002F?p=2550","2018-08-28 00:18:34 +0000",{"type":8,"value":373},[374],[11,375,317],{},"\u002F2018-08-28-old-mans-journy",{"title":312,"description":317},"_legacy\u002F2018\u002F2018-08-28-old-mans-journy",[],"W3Y5EtcyDTsDCvFBmp1vuLPwd8iA8g8ZOcr94TPrWG4",{"id":382,"title":383,"body":384,"date":523,"description":388,"extension":289,"meta":524,"navigation":293,"path":533,"seo":534,"stem":535,"tags":536,"__hash__":539},"blogs\u002F_legacy\u002F2018\u002F2018-07-28-ue4-tick-note.md","UE4中Tick使用小结",{"type":8,"value":385,"toc":512},[386,389,392,395,398,401,404,407,411,414,417,420,423,427,430,433,437,440,443,446,450,453,456,462,465,469,472,476,479,482,485,491,494,500,503,509],[11,387,388],{},"Tick在UE4的逻辑中是最基本的单元之一，不过也有些地方需要注意的。",[11,390,391],{},"当前使用的UE4版本为4.18。",[18,393,394],{"id":394},"并行",[11,396,397],{},"Tick是可以打开并行的，对于一些和主要逻辑没有耦合的逻辑，可以打开该Object的并行Tick：bRunOnAnyThread来进行运行优化。",[11,399,400],{},"实际并行的执行方式可以到FTickTaskSequencer::ReleaseTickGroup中去查看。",[11,402,403],{},"并行只在TickGroup内部执行，但是要小心多线程同步问题，考虑到加锁的成本，还是只对独立的逻辑进行异步比较好。",[11,405,406],{},"在优化上，还有一个bAllowTickOnDedicatedServer的选项，不过具体与使用_Server宏来屏蔽代码相比哪个更好些并没有测试过~",[18,408,410],{"id":409},"actualtickgroup","ActualTickGroup",[11,412,413],{},"每个TickFunc都可以指定TickGroup，但是这个TickGroup并不代表最终实际执行的TickGroup。",[11,415,416],{},"根据每个Tick注册的Prerequisite不同，Tick的执行Group会被延迟。",[11,418,419],{},"例如，如果TickFunc要求的Prerequisite是在PostPhiscs中的，那么即便它自己是注册为PrePhyiscs，也会被推迟到Post才能执行。",[11,421,422],{},"这个时候可以看到TickFunc的ActualStartTickGroup会变成实际的Tick执行组。",[18,424,426],{"id":425},"自定义tick","自定义Tick",[11,428,429],{},"可以通过自定义TickFunc来构建自己的Tick逻辑，相比TimerManager最大的优势就是这样可以在原有的Tick之外获得自己需要的逻辑执行时间。",[11,431,432],{},"引擎内部有一些额外定义的TickFunc，以前似乎是有SecondaryTick的，不过后来弃用了。",[96,434,436],{"id":435},"uworld","UWorld",[11,438,439],{},"作为PhsxScene的维护者，拥有额外的三个TickFunc：FStartPhysicsTickFunction、FEndPhysicsTickFunction、FStartAsyncSimulationFunction。",[11,441,442],{},"都是用于对物理世界进行操作的Tick，其中StartAsyncSimulation是用于cloth的模拟的，注册在EndPhysicsTickFunction之后。而StartPysics是用于启动物理模拟，EndPhysics则是物理模拟结束的。",[11,444,445],{},"所以如果要保证物理处理已经完成，最好是注册到PostPhysics。",[96,447,449],{"id":448},"uskeletalmeshcomponent","USkeletalMeshComponent",[11,451,452],{},"FSkeletalMeshComponentEndPhysicsTickFunction是注册在EndPhysics的，用于骨骼动画的状态同步等操作，没有仔细的看过，详情参考USkeletalMeshComponent::EndPhysicsTickComponent。",[11,454,455],{},"这个Tick是要求在关卡完成物理结算之后的：",[82,457,460],{"className":458,"code":459,"language":87},[85],"EndPhysicsTickFunction.AddPrerequisite(World, World->EndPhysicsTickFunction);\n",[89,461,459],{"__ignoreMap":91},[11,463,464],{},"FSkeletalMeshComponentClothTickFunction则是负责对Cloth进行更新的，它被添加到了SkeletalMesh的EndPhysicsTickFunction之后，保证物理更新完成之后再进行衣服的演算更新。",[96,466,468],{"id":467},"ucharactermovmentcomponent","UCharacterMovmentComponent",[11,470,471],{},"FCharacterMovementComponentPostPhysicsTickFunction这个是在角色移动中，执行PostPhysicsTickComponent，当bDeferUpdateBasedMovement打开了的话，会在这里进行一次移动更新。",[96,473,475],{"id":474},"uprimitivecomponent","UPrimitiveComponent",[11,477,478],{},"FPrimitiveComponentPostPhysicsTickFunction，这个是用来执行每个Component的PostPhysicsTick的，按照官方注释已经弃用。",[96,480,481],{"id":481},"自定义",[11,483,484],{},"参照上面的TickFunc的用法，自定义比较简单",[82,486,489],{"className":487,"code":488,"language":87},[85],"USTRUCT()\nstruct FMyCustomTick : public FTickFunction\n{\n  GENERATED_USTRUCT_BODY()\n\n  class UVehicleSyncComponent*    Target;\n\n  FMyCustomTick();\n\n  virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override;\n  \u002F** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **\u002F\n  virtual FString DiagnosticMessage() override;\n};\n\ntemplate\u003C>\nstruct TStructOpsTypeTraits\u003CFMyCustomTick> : public TStructOpsTypeTraitsBase2\u003CFMyCustomTick>\n{\n  enum\n  {\n    WithCopy = false\n  };\n};\n",[89,490,488],{"__ignoreMap":91},[11,492,493],{},"其中，下面的宏是必须的，否则会导致编译出错。因为TickFunc是不允许复制的。之后再对函数进行实现：",[82,495,498],{"className":496,"code":497,"language":87},[85],"FMyCustomTick::FMyCustomTick()\n{\n  TickGroup = ETickingGroup::TG_PostPhysics;\n  bCanEverTick = true;\n}\n\nvoid FMyCustomTick::ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)\n{\n  FActorComponentTickFunction::ExecuteTickHelper(Target, \u002F*bTickInEditor=*\u002F false, DeltaTime, TickType, [this](float DilatedTime) { Target->PostPhysicsTick(*this); });\n}\n\nFString FMyCustomTick::DiagnosticMessage()\n{\n  return Target->GetFullName() + TEXT(\"[UMyComp::PostPhysxTick]\");\n}\n",[89,499,497],{"__ignoreMap":91},[11,501,502],{},"对于Component，定义了FMyCustomTick的变量PostPhysxComponentTick之后，在其Tick注册函数RegisterComponentTickFunctions中继续注册就可以了，可以使用的帮助函数SetupActorComponentTickFunction来进行注册，以及注意解注册：",[82,504,507],{"className":505,"code":506,"language":87},[85],"if (bRegister)\n{\n  if (SetupActorComponentTickFunction(&PostPhysxComponentTick))\n  {\n    PostPhysxComponentTick.Target = this;\n\n    UWorld* World = GetWorld();\n    if (World != nullptr)\n    {\n      PostPhysxComponentTick.AddPrerequisite(World, World->EndPhysicsTickFunction);\n    }\n  }\n}\nelse\n{\n  if (PostPhysxComponentTick.IsTickFunctionRegistered())\n  {\n    PostPhysxComponentTick.UnRegisterTickFunction();\n  }\n}\n",[89,508,506],{"__ignoreMap":91},[11,510,511],{},"TickFunc的自定义还是比较简单的 ，不明白的地方可以参考上面那些引擎内部用法。",{"title":91,"searchDepth":277,"depth":278,"links":513},[514,515,516],{"id":394,"depth":277,"text":394},{"id":409,"depth":277,"text":410},{"id":425,"depth":277,"text":426,"children":517},[518,519,520,521,522],{"id":435,"depth":278,"text":436},{"id":448,"depth":278,"text":449},{"id":467,"depth":278,"text":468},{"id":474,"depth":278,"text":475},{"id":481,"depth":278,"text":481},"2018-07-28",{"layout":291,"status":292,"published":293,"author":525,"author_login":296,"author_email":297,"wordpress_id":526,"wordpress_url":527,"date_gmt":528,"excerpt":529},{"display_name":295,"login":296,"email":297,"url":91},2409,"\u002F?p=2409","2018-07-28 02:03:37 +0000",{"type":8,"value":530},[531],[11,532,388],{},"\u002F2018-07-28-ue4-tick-note",{"title":383,"description":388},"_legacy\u002F2018\u002F2018-07-28-ue4-tick-note",[537,538],"UE4","Tick","WcoVUqA7rnnKC0CuEd2pU1q4cND6cJ72eY60f8a31Hc",{"id":541,"title":542,"body":543,"date":972,"description":547,"extension":289,"meta":973,"navigation":293,"path":982,"seo":983,"stem":984,"tags":985,"__hash__":987},"blogs\u002F_legacy\u002F2018\u002F2018-07-26-ue4-loading-gc-optimize.md","UE4中Loading和GC的优化",{"type":8,"value":544,"toc":949},[545,548,557,561,564,568,571,576,579,582,601,604,608,611,614,617,620,623,629,632,635,643,646,650,653,656,659,664,667,670,673,678,681,684,687,701,704,707,710,716,719,722,725,728,737,740,743,748,751,754,758,761,764,767,770,773,776,779,783,786,791,794,799,802,807,810,813,819,822,827,830,833,836,840,843,848,851,854,860,863,869,872,875,879,882,885,890,893,896,899,902,907,910,913,916,920,923,937,940,943,946],[11,546,547],{},"本文翻译整理自Epic Japan分享的Slide，是官方的一些优化建议。",[11,549,550,551,556],{},"原始的PPT请参看[",[195,552,555],{"href":553,"rel":554},"https:\u002F\u002Fwww.slideshare.net\u002FEpicGamesJapan\u002Fue4loadinggcprofiling",[199],"这里","]。",[18,558,560],{"id":559},"loading-time","Loading Time",[11,562,563],{},"载入时间在逻辑上分为两个部分：从硬盘中将需要的资源载入内存的时间；以及在AddToWorld时对系统造成的瞬间负载时间。",[96,565,567],{"id":566},"stat-level","Stat Level",[11,569,570],{},"通常要对载入状态进行追踪，可以使用Stat Level指令。",[11,572,573],{},[39,574],{"alt":41,"src":575},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb.png",[11,577,578],{},"这个指令会将关卡的载入情况显示出来，不过对于没有使用流关卡的情况，似乎作用不大。",[11,580,581],{},"这里显示出来的就是各个关卡载入的情况，总共有五种情况对应不同的颜色：",[583,584,585,589,592,595,598],"ul",{},[586,587,588],"li",{},"灰色-Persistent level",[586,590,591],{},"红色-UnLoad level",[586,593,594],{},"紫色-Loading",[586,596,597],{},"橙色-AddToWorld",[586,599,600],{},"绿色-Loaded Level",[11,602,603],{},"通常使用MemReport指令输出的关卡状态，由于是命令瞬间的状态，通常只会看到UnLoadLevel和Loaded Level两种常驻状态。",[96,605,607],{"id":606},"loadtimes","Loadtimes",[11,609,610],{},"要对载入情况进行更加精细的分析，可以借助官方提供的Loadtimes指令。",[11,612,613],{},"使用LoadTimes.DumpReport可以将关卡的详细载入时间消耗情况输出到log，如果使用LoadTimes.DumpReport FILE的话就可以将报告输出到\\Saved\\Profiling\\LoadReports\\目录。",[11,615,616],{},"此外LoadTimes.DumpReport还可以使用LOWTIME=0.05等来过滤小于这个时间的载入报告，方便排查大的时间消耗。",[11,618,619],{},"而使用Loadtimes.reset可以对累计的数据进行清理，避免整个测试跑得太久累计下来的不需要的信息。",[11,621,622],{},"整个LoadTimes的统计核心是在UObjectGlobals.cpp中的，例如在Endload中这样的Scope：",[82,624,627],{"className":625,"code":626,"language":87},[85],"\u002F\u002F The time tracker keeps track of time spent in EndLoad.\nFExclusiveLoadPackageTimeTracker::FScopedEndLoadTracker Tracker;\n",[89,628,626],{"__ignoreMap":91},[11,630,631],{},"所有的追踪由单例FExclusiveLoadPackageTimeTracker负责，有兴趣的可以详细的阅读其代码。",[11,633,634],{},"报告大体上可以分为下面的两个部分",[583,636,637,640],{},[586,638,639],{},"asset type load times sorted by exclusive time: 按类型排列的载入时间统计(s)",[586,641,642],{},"Dumping all loaded assets by exclusive\u002Finclusive load time: 按具体的资源排列(ms)",[11,644,645],{},"Exclusive的统计不包括具体的Object的依赖资源，而Inclusive是包含所有依赖资源的。",[96,647,649],{"id":648},"addtoworld","AddToWorld",[11,651,652],{},"在加载过程中AddToWorld是最后一个过程，物体在载入之后必须注册到渲染世界和物理世界中才能正常的进行游戏。",[11,654,655],{},"要对这个过程进行记录必须打开PERF_TRACK_DETAILED_ASYNC_STATS宏才行，打开之后可以对FAsyncPackage的CreateExport和PostLoad以及World中的AddToWorld和RemoveFromWorld获得更多的信息。",[11,657,658],{},"输出的内容会有像是这样的：",[11,660,661],{},[39,662],{"alt":41,"src":663},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-1.png",[11,665,666],{},"不过实际上感觉用处不是很大~",[96,668,669],{"id":669},"优化",[11,671,672],{},"优化载入时间首先的建议是使用官方的PAK机制，社区也有看到使用7zip之类的压缩算法来进行内容压缩的。不过压缩率是一个需要权衡的地方，因为虽然能够缩短载入到内存的时间以及减小包尺寸，却会导致额外的解压缩成本。",[674,675,677],"h4",{"id":676},"fileopenorder","FileOpenOrder",[11,679,680],{},"在载入时间优化方面，官方在PAK机制内还提供了一个FileOpenOrder的优化工具。",[11,682,683],{},"要使用这个工具，在运行游戏实例时加上-flieopenorder指令。然后在游戏中将常规操作都执行一遍并退出游戏。这时候会生成一个GameOpenOrder.log，将这个文件拷贝到\u002FBuild\u002FWindowsNoEditor\u002FFileOpenOrder\u002F ，这样重新打包的时候就可以得到优化的文件顺序了。之后根据项目的迭代不断的更新这个文件就可以了。",[11,685,686],{},"这个优化主要是为了减少在对资源进行载入的时候的寻道成本了，另外这个优化对于SD卡这样的Mobile其实也是有优化作用的。因为SD卡的文件碎片虽然不会导致机械硬盘那样的延迟，也会导致读取效率的下降。",[253,688,689,692],{},[11,690,691],{},"File fragmentation: where there is not sufficient space for a file to be recorded in a contiguous region, it is split into non-contiguous fragments. This does not cause rotational or head-movement delays as with electromechanical hard drives, but may decrease speed; for instance, by requiring additional reads and computation to determine where on the card the file's next fragment is stored.",[11,693,694,695,700],{},"[",[195,696,699],{"href":697,"rel":698},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSecure_Digital",[199],"Secure Digital Card","]",[674,702,703],{"id":703},"减少尺寸",[11,705,706],{},"除此之外的就都是一些常见的优化建议了，例如Shader Permutation Reduction 、材质的Instance策略等。",[11,708,709],{},"以及一些在不使用的情况下可以去掉的Index Buffer:",[82,711,714],{"className":712,"code":713,"language":87},[85],"\u002F** Reversed depth only index buffer, used to prevent changing culling state between drawcalls. *\u002F\nFRawStaticIndexBuffer ReversedDepthOnlyIndexBuffer;\n\n\u002F** Index buffer containing adjacency information required by tessellation. *\u002F\nFRawStaticIndexBuffer AdjacencyIndexBuffer;\n\n\u002F** Reversed index buffer, used to prevent changing culling state between drawcalls. *\u002F\nFRawStaticIndexBuffer ReversedIndexBuffer;\n",[89,715,713],{"__ignoreMap":91},[11,717,718],{},"其中AdjacencyIndexBuffer是为tessellation而提供的，如果不用的话可以关掉，而ReversedIndexBuffer似乎是为了在DrawCall之间减少culling state计算的成本而存在的，实际在渲染中的作用没有仔细的考据。",[11,720,721],{},"代码中能看到的注释是provide a minor rendering speedup at the expense of using twice the index buffer memory，这方面的权衡就需要实际测试了。",[674,723,724],{"id":724},"加载控制",[11,726,727],{},"通过异步，将资源加载推迟或者提前。在4.19后Asset Manager应当已经可以使用了，可以通过Asset Manager对资源的加载进行更好的控制。",[11,729,730,731,736],{},"详情可以参考官方的[",[195,732,735],{"href":733,"rel":734},"http:\u002F\u002Fapi.unrealengine.com\u002FCHN\u002FEngine\u002FBasics\u002FAssetsAndPackages\u002FAssetManagement\u002Findex.html",[199],"资源管理","]文档。",[11,738,739],{},"传统上的做法，还包括使用软指针来防止蓝图自动加载关联资源等手段。",[11,741,742],{},"同时，针对地图的Stream In\u002FOut官方还提供了一些异步化的优化选项：",[11,744,745],{},[39,746],{"alt":41,"src":747},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-2.png",[11,749,750],{},"将Object的加载分散到各个帧去，而不会因为AddToWorld操作导致单帧瞬间卡顿。",[11,752,753],{},"另外，蓝图中尽量将逻辑从Begin Play移动到Construction Script中去，可以享受CDO带来的速度加成。",[18,755,757],{"id":756},"gc","GC",[11,759,760],{},"UE4内部的垃圾回收系统理论上除了提供的优化接口之外应当最好不要手动修改，由于GC本身要照顾很多方面，所以其中必然会有权衡存在。",[11,762,763],{},"通常GC会引起问题都是在LevelStreaming的时候，不过如果你在代码中申请了大规模的UObject，在不再使用的时候也不删除。或者频繁而无意义的调用ForceGC，自然也可能会造成性能问题。",[11,765,766],{},"UE4通过一张链表进行GC维护基础，数据类型为FUObjectArray。",[11,768,769],{},"GC的部分逻辑可以到GarbageCollection.cpp中进行查看。",[11,771,772],{},"通常情况下GC采用多线程的形式，但是当可用的处理器核心为1或者关闭了并行GC的时候就会变成单线程的形式。另外，Per Class的GC被打开的时候，由于无法保证线程安全，也会导致GC变为单线程。目前在编辑器中，由于HandleObjectReference中的Modify()的存在，是强制单线程进行的。",[11,774,775],{},"上面的内容来自官方的注释，由于不打算深入GC逻辑的修改，所以没有考据为何编辑器中Modify无法被移除。",[11,777,778],{},"GC的主要成本来来自遍历成本和实际的Object删除，4.16更新的GC优化就是将Object的实际删除分散到了各个帧中，这样在GC执行的时候就只需要执行依赖关系的切断了。",[96,780,782],{"id":781},"profiling","Profiling",[11,784,785],{},"要对GC的实际消耗进行查看的话，可以打开log LogGarbage log",[11,787,788],{},[39,789],{"alt":41,"src":790},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-3.png",[11,792,793],{},"也可以使用Stat dumphitches来定位GC造成的瞬间延迟",[11,795,796],{},[39,797],{"alt":41,"src":798},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-4.png",[11,800,801],{},"通常使用FrontEnd的时候会看到的警告",[11,803,804],{},[39,805],{"alt":41,"src":806},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-5.png",[11,808,809],{},"可以使用-NOVERIFYGC指令运行来去掉。",[11,811,812],{},"对于需要进一步的GC的信息的情况，可以选择打开",[82,814,817],{"className":815,"code":816,"language":87},[85],"# define PROFILE_GCConditionalBeginDestroy 1\n# define PROFILE_GCConditionalBeginDestroy_byClass 1\n",[89,818,816],{"__ignoreMap":91},[11,820,821],{},"来输出更加详细的信息：",[11,823,824],{},[39,825],{"alt":41,"src":826},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-6.png",[96,828,669],{"id":829},"优化-1",[11,831,832],{},"为了避免LevelStreaming造成的GC瞬间成本，可以通过缩小细分关卡的规模来进行。",[11,834,835],{},"这样的话在进行角色的移动时，就可以形成小规模的载入和移除，而不是一次性的大规模变更。",[674,837,839],{"id":838},"disregardgcobject","DisregardGCObject",[11,841,842],{},"另外，针对其实不需要进行GC的一些常驻Object，可以通过",[11,844,845],{},[39,846],{"alt":41,"src":847},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-7.png",[11,849,850],{},"选项来进行限制，这样的话就可以有效的减少遍历成本。",[11,852,853],{},"针对这个选项，官方有提供辅助设定的工具，不需要自己进行试错",[82,855,858],{"className":856,"code":857,"language":87},[85],"[\u002FScript\u002FEngine.GarbageCollectionSettings]\ngc.MaxObjectsNotConsideredByGC=1\ngc.SizeOfPermanentObjectPool=0\n",[89,859,857],{"__ignoreMap":91},[11,861,862],{},"在设定中这样设置后，游戏开启后会在log中输出",[82,864,867],{"className":865,"code":866,"language":87},[85],"LogUObjectArray: 52083 objects as part of root set at end of initial load.\nLogUObjectAllocator: 9937152 out of 0 bytes used by permanent object pool.\n",[89,868,866],{"__ignoreMap":91},[11,870,871],{},"这样就可以得到一个有效的设定值了。",[11,873,874],{},"在GarbageCollectionSettings中还有一些其他的设定值，以及用于gc的console选项，不过一般情况应当不会动到。",[674,876,878],{"id":877},"cluster","Cluster",[11,880,881],{},"对于复合性的逻辑物体，其内部的Object随着父物体的状态进行管理的，可以使用Cluster来进行GC管理。",[11,883,884],{},"防止不必要的对很多子物体进行GC遍历：",[11,886,887],{},[39,888],{"alt":41,"src":889},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-8.png",[11,891,892],{},"其中，Merge GC Clusters可以运行聚合之间相互构成更大的聚合。",[11,894,895],{},"Actor的话需要打开Can be in Cluster，StaticMeshActor是默认打开的。因为其中引用的很多资源基本是跟随其自身的生命周期的。",[11,897,898],{},"Cluster由于是在AddToWorld中进行的，所以在Sequence中可能会有问题。",[11,900,901],{},"下面的是官方给出的优化结果：",[11,903,904],{},[39,905],{"alt":41,"src":906},"\u002Fwp-content\u002Fuploads\u002F2018\u002F07\u002Fimage_thumb-9.png",[674,908,909],{"id":909},"减少规模",[11,911,912],{},"这个算是通用的建议，例如蓝图的Macros在使用上需要谨慎，因为可能会造成在打包展开时变成很大的结构，应当尽可能的函数化。",[11,914,915],{},"还有就是尽可能的使用Blueprint Nativization，但是这个似乎一直没有离开实验阶段……",[96,917,919],{"id":918},"finishdestroy的帧分散","FinishDestroy的帧分散",[11,921,922],{},"帧分散的思路可以用在LevelStreaming的StreamOut中FinishDestroy的工作分散掉：",[253,924,925],{},[926,927,928,931,934],"ol",{},[586,929,930],{},"void UWorld::UpdateLevelStreaming()中ForceGarbageCollection(true); 改为false。",[586,932,933],{},"UWorld* UWorld::FindWorldInPackage()中GetObjectsWithOuter改为GetObjectsWithOuter(Package, PotentialWorlds, false, EObjectFlags::RF_NoFlags,EInternalObjectFlags::PendingKill);。",[586,935,936],{},"UWorld* UWorld::FollowWorldRedirectorInPackage()中GetObjectsWithOuter改为GetObjectsWithOuter(Package, PotentialRedirectors, false,EObjectFlags::RF_NoFlags,EInternalObjectFlags::PendingKill);",[11,938,939],{},"这个是EpicJapan向Epic官方确认会有效果的一种优化方式。",[18,941,942],{"id":942},"总结",[11,944,945],{},"由于是官方的PPT，所以内容上都是一些通用的建议，以及官方本身的优化工具的介绍。",[11,947,948],{},"实际项目优化中大概都会有不少的引擎魔改，不过参考一下官方的思路，把基础的优化功能打开也是很重要的~",{"title":91,"searchDepth":277,"depth":278,"links":950},[951,962,971],{"id":559,"depth":277,"text":560,"children":952},[953,954,955,956],{"id":566,"depth":278,"text":567},{"id":606,"depth":278,"text":607},{"id":648,"depth":278,"text":649},{"id":669,"depth":278,"text":669,"children":957},[958,960,961],{"id":676,"depth":959,"text":677},4,{"id":703,"depth":959,"text":703},{"id":724,"depth":959,"text":724},{"id":756,"depth":277,"text":757,"children":963},[964,965,970],{"id":781,"depth":278,"text":782},{"id":829,"depth":278,"text":669,"children":966},[967,968,969],{"id":838,"depth":959,"text":839},{"id":877,"depth":959,"text":878},{"id":909,"depth":959,"text":909},{"id":918,"depth":278,"text":919},{"id":942,"depth":277,"text":942},"2018-07-26",{"layout":291,"status":292,"published":293,"author":974,"author_login":296,"author_email":297,"wordpress_id":975,"wordpress_url":976,"date_gmt":977,"excerpt":978},{"display_name":295,"login":296,"email":297,"url":91},2390,"\u002F?p=2390","2018-07-25 19:57:21 +0000",{"type":8,"value":979},[980],[11,981,547],{},"\u002F2018-07-26-ue4-loading-gc-optimize",{"title":542,"description":547},"_legacy\u002F2018\u002F2018-07-26-ue4-loading-gc-optimize",[537,757,986],"Loading","GevD6rMhLCgBoNhVGXgJ0gK6aRt2AwgsXRLezCEdlNw",{"id":989,"title":990,"body":991,"date":1119,"description":995,"extension":289,"meta":1120,"navigation":293,"path":1129,"seo":1130,"stem":1131,"tags":1132,"__hash__":1134},"blogs\u002F_legacy\u002F2018\u002F2018-05-09-ue4-and-sol2.md","UE4中Sol2的接入",{"type":8,"value":992,"toc":1113},[993,996,999,1002,1011,1015,1018,1021,1024,1027,1030,1033,1036,1040,1043,1046,1049,1052,1055,1061,1064,1068,1071,1074,1080,1083,1086,1089,1092,1095,1101,1104],[11,994,995],{},"虽然说Lua的C++接入本身在网上能找到很多示例，但是在尝试对Sol2进行接入的时候还是遇到了一些问题，所以在此进行记录。",[11,997,998],{},"当前使用的UE4版本为4.19.1。",[11,1000,1001],{},"Sol2是进行Lua绑定的C++类库，过程中最主要的问题是，对Lua本身一知半解，所以没有很好的理解Sol2官方文档中所描述的一些术语。",[11,1003,1004,1005,1010],{},"之所以会选择Sol2，是因为它号称自己是[",[195,1006,1009],{"href":1007,"rel":1008},"http:\u002F\u002Fsol2.readthedocs.io\u002Fen\u002Flatest\u002Fbenchmarks.html",[199],"地上最快","]的~",[18,1012,1014],{"id":1013},"luajit","LuaJit",[11,1016,1017],{},"似乎lua本身的脚本解释器有两个公开的版本，一个是LuaJit，一个是vanilla Lua。",[11,1019,1020],{},"由于没有仔细的对这两者之间的差别进行区分，所以并不是特别清楚其中的具体区别。",[11,1022,1023],{},"直接Google的话，Lua是指向vanilla lua的，而且LuaJit的Lua版本也比较靠前。",[11,1025,1026],{},"但是由于Sol2的官方文档中说LuaJit的速度会比较快，所以就采用了LuaJit。",[11,1028,1029],{},"那么首先第一步就是下载LuaJit，到了这一步就比较明了了。",[11,1031,1032],{},"LuaJit有提供CMake的配置文件，用CMake配置一下之后就可以进行生成了。",[11,1034,1035],{},"对于下一步的接入需要的是生成的Lua51.lib和Lua51.dll。",[18,1037,1039],{"id":1038},"sol2","Sol2",[11,1041,1042],{},"这个库本身也有提供CMake，直接使用CMake配置就好了。",[11,1044,1045],{},"但是遇到的主要问题是，在和LuaJit的对接上，不过实际上在对整个库进行理清之后就不会有什么问题。",[11,1047,1048],{},"Sol2有提供很多的Test所以在使用方面会有很多的便利。",[11,1050,1051],{},"不过这里用CMake进行配置其实是不必要的，Sol2本身有提供单文件版本，其实只要include就可以了。",[11,1053,1054],{},"但是LuaJit的话还是需要进行链接的，并且必须根据使用的Lua解释器的不同来提供不同的宏。基本这样包含就可以了：",[82,1056,1059],{"className":1057,"code":1058,"language":87},[85],"extern \"C\"\n{\n#include \u003Clua.h>\n#include \u003Clualib.h>\n#include \u003Clauxlib.h>\n}\n\n#define SOL_USING_CXX_LUA        1\n#define SOL_USING_CXX_LUAJIT    1\n#include \"sol.hpp\"\n",[89,1060,1058],{"__ignoreMap":91},[11,1062,1063],{},"最主要的是前面的Extern “C”不能忘记，不然会报很多的链接错误~",[18,1065,1067],{"id":1066},"ue4插件接入","UE4插件接入",[11,1069,1070],{},"据说以前Sol2接入的话会有一些问题，因为sol2本身对check有定义，所以会造成定冲突。",[11,1072,1073],{},"于是sol2的作者后面给出了解决方案：",[82,1075,1078],{"className":1076,"code":1077,"language":87},[85],"#if defined(UE_BUILD_DEBUG) || defined(UE_BUILD_DEVELOPMENT) || defined(UE_BUILD_TEST) || defined(UE_BUILD_SHIPPING) || defined(UE_SERVER)\n#define SOL_INSIDE_UNREAL\n#endif \u002F\u002F Unreal Engine 4 bullshit\n\n#ifdef SOL_INSIDE_UNREAL\n#ifdef check\n#define SOL_INSIDE_UNREAL_REMOVED_CHECK\n#undef check\n#endif\n#endif \u002F\u002F Unreal Engine 4 Bullshit\n",[89,1079,1077],{"__ignoreMap":91},[11,1081,1082],{},"总之就是回避了一下：D",[11,1084,1085],{},"不过说到UE4兼容，最近VA也新增了对UE4的特别支持。记得一开始的时候，VA经常在对UE4的函数进行跳转的时候崩掉VS，而且还有一定几率的崩掉VA建立的代码解析缓存，然后需要重新进行一次代码解析，非常的欢乐。",[11,1087,1088],{},"不过新增的UE4支持还没有完全的测试过，不知实际上有什么改进还不是特别的清楚呢~",[18,1090,1091],{"id":1091},"测试",[11,1093,1094],{},"最后在UE4中使用蓝图函数进行了简单的测试：",[82,1096,1099],{"className":1097,"code":1098,"language":87},[85],"UE_LOG(LogTemp,Log, TEXT(\"=== basic ===\"));\n\n\u002F\u002F create an empty lua state\nsol::state lua;\n\n\u002F\u002F by default, libraries are not opened\n\u002F\u002F you can open libraries by using open_libraries\n\u002F\u002F the libraries reside in the sol::lib enum class\nlua.open_libraries(sol::lib::base);\n\u002F\u002F you can open all libraries by passing no arguments\n\u002F\u002Flua.open_libraries();\n\n\u002F\u002F call lua code directly\nlua.script(\"print('hello world')\");\n\n\u002F\u002F call lua code, and check to make sure it has loaded and run properly:\nauto handler = &sol::script_default_on_error;\nlua.script(\"print('hello again, world')\", handler);\n\n\u002F\u002F Use a custom error handler if you need it\n\u002F\u002F This gets called when the result is bad\nauto simple_handler = [](lua_State*, sol::protected_function_result result) {\n\u002F\u002F You can just pass it through to let the call-site handle it\n  return result;\n};\n\u002F\u002F the above lambda is identical to sol::simple_on_error, but it's\n\u002F\u002F shown here to show you can write whatever you like\n\n\u002F\u002F\n{\n  auto result = lua.script(\"print('hello hello again, world') \\n return 24\", simple_handler);\n  if (result.valid()) {\n    UE_LOG(LogTemp, Log, TEXT(\"the third script worked, and a double-hello statement should appear above this one!\"));\n    int value = result;\n    ensure(value == 24);\n  }\n  else {\n    UE_LOG(LogTemp, Log, TEXT(\"the third script failed, check the result type for more information!\"));\n  }\n}\n\n\u002F**    \u003CThis test will log out 0xE24C4A03 *\u002F\n{\n  auto result = lua.script(\"does.not.exist\", simple_handler);\n  if (result.valid()) {\n    UE_LOG(LogTemp, Log, TEXT(\"the fourth script worked, which it wasn't supposed to! Panic!\"));\n    int value = result;\n    ensure(value == 24);\n  }\n  else {\n    sol::error err = result;\n    UE_LOG(LogTemp, Log, TEXT(\"the fourth script failed, which was intentional! nError: %s\"), ANSI_TO_TCHAR(err.what()));\n  }\n}\n",[89,1100,1098],{"__ignoreMap":91},[11,1102,1103],{},"由于接下来还没有具体的使用计划，所以就在这里了。",[11,1105,1106,1107,1112],{},"绑定好的Sol2插件可以到Github上下载：[",[195,1108,1111],{"href":1109,"rel":1110},"https:\u002F\u002Fgithub.com\u002FArisego\u002FSol2UE4",[199],"Sol2UE4","]，这样有需要的童鞋就没有必要重新再绑一遍了~",{"title":91,"searchDepth":277,"depth":278,"links":1114},[1115,1116,1117,1118],{"id":1013,"depth":277,"text":1014},{"id":1038,"depth":277,"text":1039},{"id":1066,"depth":277,"text":1067},{"id":1091,"depth":277,"text":1091},"2018-05-09",{"layout":291,"status":292,"published":293,"author":1121,"author_login":296,"author_email":297,"wordpress_id":1122,"wordpress_url":1123,"date_gmt":1124,"excerpt":1125},{"display_name":295,"login":296,"email":297,"url":91},2352,"\u002F?p=2352","2018-05-09 15:25:11 +0000",{"type":8,"value":1126},[1127],[11,1128,995],{},"\u002F2018-05-09-ue4-and-sol2",{"title":990,"description":995},"_legacy\u002F2018\u002F2018-05-09-ue4-and-sol2",[537,1039,1133],"lua","AN5DDWVMtt3GIYCq4gzzCGWkoUTmUT45hP9SOx1QSL0",222,1788763182669]